Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Subdomains and Subfolders, One Network

    Subdomains and Subfolders, One Network

    For the longest time, if someone wanted to have example.com, foo.example.com and example.com/bar for their Network on Multisite, I’d tell them to use a Multinetwork Plugin like Networks+ (which you can buy from e-Books by Ron and Andrea) or WP Multi Network (free from JJJ).

    But sometimes you don’t need multiple networks and the multiple admin sections. Sometimes you just want to have options. Thanks to the work that started with the roadmap you can have your cake and eat it too.

    If you’ll recall, I detailed how you can map a domain without a plugin on Multisite these days. Guess what? You can also do this with subfolders and subdomains.

    I did this with a subdomain install, since it made more sense to go that way.

    WordPress is installed at multisite.dev and I have subsites of foo.multisite.dev and bar.multisite.dev

    I then made a new site called baz.multisite.dev:

    Creating baz.multisite.dev

    Then I edited that from this:

    Editing baz.multsite.dev

    To this:

    Now it's multisite.dev/baz

    Be careful here! If you don’t put the trailing slash on the folder name, this will not work. And does this work? Yes it does. Of course there is the small issue of how this looks on my list of domains:

    Domain List is Ugly

    I have two sites as ‘multisite.dev’ and I have two ‘foo’ sites (because you can also make foo.multisite.dev/zot if you want to). The problem is that the Sites page in the Network Admin has a check:

    $blogname_columns = ( is_subdomain_install() ) ? __( 'Domain' ) : __( 'Path' );

    This means the ‘domain’ of foo.multisite.dev/zot and foo.multisite.dev are (correctly) foo. I couldn’t see how to filter, so I made a quick MU Plugin:

    class Add_Blog_Blogname {
    	
    	public function __construct() {
            add_filter( 'wpmu_blogs_columns', array( $this, 'blogname' ) );
            add_action('manage_sites_custom_column',  array( $this, 'blogname_columns' ) , 10, 3);
            add_action('manage_blogs_custom_column', array( $this, 'blogname_columns' ) , 10, 3);
        }
    
    	function blogname_columns($column, $blog_id) {
    	        global $wpdb;
    	        
    	        $blog_details = get_blog_details($blog_id);
    	        
    	        if ( $column == 'my_blogname' ) {
    	                echo $blog_details->blogname;
    	        }
    	        return $value;
    	}
    	
    	// Add in a column header
    	function blogname($columns) {
    	    $columns['my_blogname'] = __('True BlogName');
    	    return $columns;
    	}
    }
    
    new Add_Blog_Blogname();
    

    This tosses the True Blog Name to the end of the sites list. It’s not perfect, but it gets the job done.

  • GeoIP Options

    GeoIP Options

    Thanks to crazy thinks like the EU VAT laws, sometimes we really have to know where people are coming from when they visit our sites. The problem with this is … how?

    There’s a cool extension for PHP called GeoIP, which I’ve finally installed on this server (along with my upgrade to PHP 5.5 and some other things, yes, still on Apache, shut up Otto). The extension comes from MaxMind, who also have a pure PHP version you can use. I’m not because the GeoLite2 databases are distributed under the Creative Commons Attribution-ShareAlike 3.0 Unported License and that means I can’t include it in a WordPress plugin.

    But that really made me wonder why it was okay not to attribute Maxmind when I used it via Pecl. I mean, technically I should, right? But where and how? I ended up putting a note in my site footer, to say that the site used the Maxmind DBs, but I haven’t included any note about that in my plugin since the DBs are included in the plugin, just called if the functions are found. It’s on you to install and attribute as needed.

    Installing mod_geoip

    Installing this is simple, from a server admin perspective.

    Since you can’t use the yum install on Apache 2.4, I got to use a cPanel Custom Module, which meant running this:

    wget http://easyapache.cpanel.net/optmods/custom_opt_mod-mod_geoip.tar.gz
    tar -C /var/cpanel/easy/apache/custom_opt_mods -xzf custom_opt_mod-mod_geoip.tar.gz
    

    And then I ran an EasyApache build. That was fine, I needed to do that anyway. Once that was done, I installed the pecl for GeoIP:

    pecl install geoip
    

    Done. Optionally you can add it to apache in either your .htaccess or (better) a conf file for your whole server:

    <IfModule mod_geoip.c>
      GeoIPEnable On
      GeoIPDBFile /usr/local/share/GeoIP/GeoIP.dat
    </IfModule> 
    

    What about upgrades?

    Every month you don’t upgrade your geoIP DB, the more your site sucks. Someone quoted a statistic that every month you don’t upgrade the DB, the accuracy drops by 1.5%. I can’t validate that, but I’d believe it.

    Upgrades are fairly painless, thanks to geoipupdate, though it doesn’t include the IPv6 files for some reason. Still, being able to toss this into crontab makes my life easier:

    38 15 * * 5 /usr/local/bin/geoipupdate
    

    Of course… I did notice that there’s a new MaxMind DB Apache Module.

    If you’re on nginx, you can grab the nginx geoip module too.

    What if I can’t install PHP modules?

    By request, I’d already added in the GeoIP2 PHP API to my wee little plugin. Not everyone can use mod_geoip or mod_maxminddb, after all, so it’s good to have options. And with this option, you have the question of how to update since geoipupdate won’t work anymore.

    If you want to go hardcore, you can Auto-update your GeoIP databases with Cron via that very robust script. Or if you’re simple like me, it’s a geoip.sh script in your ~/scripts/ folder:

    #!/bin/sh
    cd /home/username/public_html/wp-content/edd-pec-geoip
    wget -q http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz
    gzip -d -f GeoLite2-Country.mmdb.gz
    

    And then I have this in my crontab:

    30 22 2 * * /home/username/scripts/geoip.sh
    

    Which is a lot easier for a lot of people.

  • Mailbag: Multiple Download Buttons

    Mailbag: Multiple Download Buttons

    Less a mailbag and more a forum question that grew from a logical question.

    How do you link ‘external’ stores from your product pages?

    Well, for me I do it by having a custom page I designed for my products, and listing three shortcodes. One for EDD (the default [purchase_link]) and then two for my own links. In non-shortcode, its like this:

    [Buy from Me][Buy from Amazon][Buy from Apple]

    The actual code is below. I try to keep things as simple as I can when I do this, and I did nick a bit of code from EDD core (you’re supposed to) to make it match. All this does is the most basic of links and it all fits in a nice little box like this:

    The sales box sits on the upper rght of the page and lists image, price,  and links.

    I like that layout. Reminds you a bit of Amazon, and there’s a reason. If you keep the space open and clear, it’s easy for a visitor to grasp their options. Also I can put below a link to my EU/VAT page and say this “Purchase disabled? Here’s why…” I don’t right now, though I probably should. I couldn’t think of how to elegantly handle making the button a link like that.

    The code

    	// EDD Buy Externally [edd_external store="iTunes" link="http://apple.com/foo" text="Add to Cart" ]
    	function edd_external_shortcode_func( $atts ) {
    		global $edd_options;
    
    	    $atts = shortcode_atts( array(
    	        'store' => '',
    	        'link'  => '',
    	        'text'	=> 'Buy Via',
    			'style' => isset( $edd_options[ 'button_style' ] ) ? $edd_options[ 'button_style' ] : 'button',
    			'color' => isset( $edd_options[ 'checkout_color' ] ) ? $edd_options[ 'checkout_color' ] : 'blue',
    			'class' => 'edd-submit'
    	    ),
    	    $atts, 'edd_external' );
    
    		// Override color if color == inherit
    		if( isset( $atts['color'] )	) {
    			$atts['color'] = ( $atts['color'] == 'inherit' ) ? '' : $atts['color'];
    		}
    
    		$content = '<a href="'.$atts&#91;'link'&#93;.'" class="edd-external edd-'.$atts&#91;'store'&#93;.'
    		 '.$atts&#91;'style'&#93;.' '.$atts&#91;'color'&#93;.' '.$atts&#91;'class'&#93;.'
    		"><span class="edd-add-to-cart-label">'.$atts['text'].' '.$atts['store'].'</a></span>';
    
    		return '<div class="edd_purchase_submit_wrapper">'.$content.'</div>';
    	}
    
    	add_shortcode( 'edd_external', 'edd_external_shortcode_func' );
    

    Like I said. Pretty basic.

  • Better Headers

    Better Headers

    I review a lot of code and by extension read a lot of source code files. One of the things that drives me to distraction is trying to sort out what a specific bit of code is licensed. Now, I don’t care about GPL the way a lot of people do. When I’m reviewing plugins for WordPress.org, I only care in that a repository requirement is that all code in your plugin is GPLv2 (or later) compatible. If you’re not hosting on WordPress.org, I only care if I want to use your code and I need to see what the possible restrictions are.

    While I’m mostly talking about JS files here, I wish people would remember to put in the right header information for all their code.

    Headers I Hate

    It absolutely kills me to see this as the header of a JS file:

    /*! My Awesome JS - v1.0.0 - 2015-01-14
    * http://example.com/myawesomejs/
    * Copyright (c) 2015 John Doe */
    

    Actually, what’s worse is this:

    /*! My Awesome JS - v1.0.0 - 2015-01-14 */
    

    This means I know nothing about your code license. If I have the URL, maybe I can be really lucky and go there, see a link to your full source code, download, open the zip, and maybe you put a license file in there. Most often, however, the answer to that is not. With a lot of newer projects, they link to Github, which is great since I can go and look for that LICENSE file. Github even prompts you about making one when you create a project. Love ’em

    Realistically, if you’re releasing code for the world to use then you’ve got to license it. Even if you don’t care about licenses, putting a public-domain clause on your code means it’s free for all nations to use is so easy a caveman could do it.

    Headers I Love

    Okay, so if you’re making your own JS, what do I think your header should look like?

    /**!
     * My Awesome JS v1.0.0
     *
     * @copyright Copyright 2015 John Doe
     * @author    John Doe
     * @link      http://example.com/myawesomejs/
     *            http://github.com/johndoe/myawesomejs/
     *
     * @license   Use permitted under terms of CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
     *            https://creativecommons.org/publicdomain/zero/1.0/
     *            http://example.com/myawesomejs#license
     */
    

    That’s it. Obviously that’s the non-minified version. If I wanted a minified version it would be this:

    /*! My Awesome JS v1.0.0 | (c) 2015 John Doe | example.com/myawesomejs#license */
    

    There’s an argument to be made about minification and how I shouldn’t have any headers in order to compress the code to the extreme, but let me show you the jQuery minified file’s header:

    /*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
    

    It covers your license and your copyright information there in one go. It protects your interests and makes it plain for everyone to see what’s going on, how to get in touch with you for changes, and all sorts of awesome open-source things.

    Picking a license…

    In the above example, I picked CC0 for a reason.

    That’s the ‘unlicense’ I think you should be using if you’re releasing code to the world and don’t want to be bothered by the hassles of any informal sort of license. Sadly, not declaring a real license can cause problems outside the United States. Here, we would interpret the license based on what the author intends, which is already a bit of a dangerous idea, and presume a license means exactly what it says, which makes them non-copyleft free software licenses and compatible with most other free software licenses. It may make it outright incompatible with commercial software. And worse, other countries have stringent views on the copyright aspects of these licenses.

    Now that said, my other favorite ‘For god’s sake, just take my code’ license is the WTFPL license.

    For me, if I want to release code that really is free and can and will be used by anyone, I pick the CC0 or WTFPL. It covers this for everyone, as far as I’ve been able to tell, even commercial.

    The GNU have their own license recommendations of course, and Jeff Atwood has a good post about picking a license that explains how most of us feel about the headache. Of course, if you’re forking someone else’s code or building on their work, check the license they used to make sure you don’t violate it with your new license. Not all licenses are ‘backwards compatible.’

  • Listing Everything on DreamObjects

    Listing Everything on DreamObjects

    I was asked about listing everything in a DreamObjects bucket, which is practically the same thing as listing everything in an Amazon S3 bucket, just on a different server.

    This isn’t as easy as we’d like, because while there is a nice Amazon S3 PHP class we can use (and I forked it and flipped it to be a DreamObjects PHP Class), telling people they have to download the S3.php file and then make this separate PHP file is a little annoying.

    //include the S3 class
    if (!class_exists('S3'))require_once('S3.php');
    
    //AWS access info
    if (!defined('awsAccessKey')) define('awsAccessKey', 'MYACCESSKEY');
    if (!defined('awsSecretKey')) define('awsSecretKey', 'MYSECRETKEY');
    
    //Bucket Info
    $bucketName = 'MYBUCKETNAME';
    
    /* STOP EDITING HERE */
    
    //instantiate the class
    $s3 = new S3(awsAccessKey, awsSecretKey);
    
    
    // Get the contents of our bucket
    $bucket_contents = $s3->getBucket($bucketName);
    
    foreach ($bucket_contents as $file){
    
        $fname = $file['name'];
        $furl = 'http://'.$bucketName.'.objects.dreamhost.com/'.$fname;
    
        //output a link to the file
        echo '<a href=\'$furl\'>$fname</a><br />';
    }
    

    I know, it’s not too terribly complicated, but it is annoying to make two files for that, and my DreamObjects listing page is very barebones. You could style it with CSS, make it show the image, make it show in a grid, or even show the file size and have it pop up in a new window.

    It does highlight one of my issues with CEPH (AWS whatever) storage, and that it’s still not quite friendly enough. It dumps us back to the old days where we FTP’d our images up to a server and then manually crafted our links. Certainly there are plugins for WordPress that can help us with that, but in general it requires knowing code (or where to get the code) in order to display content.

  • Multiple Domains, Multiple Logins

    Multiple Domains, Multiple Logins

    Every month or so, someone asks me why they have to log in again on multiple domains on WordPress. That is to say, they’re using Multisite and they log in to example.com and then they have to log in again on sub.example.com and this is weird.

    The answer is due to cross-domain browser protection. This is not to say you can’t do it! If you’re just using subdomains, this is really easy:

    define( 'COOKIE_DOMAIN', 'example.com' );
    define( 'ADMIN_COOKIE_PATH', '/' );
    define( 'COOKIEPATH', '/' );
    define( 'SITECOOKIEPATH', '/' );
    define( 'COOKIEHASH', md5('http://example.com') );
    

    The last one is just to prevent conflicts with other sites you may have on example.com that aren’t WordPress related. Or maybe are, but are a separate install for whatever reason.

    But if you’ve read my older posts, you know my COOKIE_DOMAIN is set like this:

    define( 'COOKIE_DOMAIN', $_SERVER[ 'HTTP_HOST' ] );
    

    That’s because I’m mapping domains without a plugin to handle that for me. And that means I have to log in separately to halfelf.org and ipstenu.org and it sucks.

    Like I said before, this is called cross-domain browser protection. You can’t use a cookie on multiple sites, even with integrated logins, with different domains.

    Point in case. The exact same user ID/Password I use on wordpress.org is used on buddypress.org and bbpress.org and I have to log in to each site separately.

    Why? To stop evil people from being evil. Can you imagine what would happen if someone sorted out your cookie hash and was able to let your login work on their sites? That would introduce new levels of phishing scam hells because you would be able to go to fake-paypal.com and your paypal.com login would just magically log you in.

    So at this point it looks like you can’t have your cookies magically work for multiple domains and automagically log you in to them without interaction. But you’re safer this way. But what if you could?

    $cookiehash = md5("http://www.example.com/");
    define('COOKIE_DOMAIN', false);
    define('COOKIEPATH', '/');
    define('SITECOOKIEPATH', '/');
    define('ADMIN_COOKIE_PATH', '/');
    define('COOKIEHASH',  $cookiehash );
    

    Notice how I changed the COOKIE_DOMAIN? Without it being defined, it doesn’t restrict the cookie to one domain. The HASH will protect you ‘enough’ and you should be able to log in on all domains on your network.

    Mind, I don’t do that. It doesn’t work reliably in my experience, which makes sense. It’s just not as safe.