Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Beta Testers

    Beta Testers

    There’s a weird aspect to open source that can be hard to explain, and that is the ethos and practicality of Beta Testing. Beta Testing is one of the most important aspects of open source, because it’s only with the beta that we’re able to real-world test products. No matter how much planning and testing and automated tests you write, there is nothing quite as powerful as the real-world. We just can’t reproduce it well enough because, among other things, we can’t yet predict humans very well. Sure, cold reading is a thing because humans can be predictable, but that doesn’t mean we know how a tool will be used 100% of the time.

    And being a beta tester is hard work. You have to be both a user and a developer, which are really two totally divergent mindsets. Testing as a user versus testing as a developer is as different as how you and a formula one racer drives a car. Input from both the users and the developers is critical to the growth of a project and the end results. But before we get into how one really betas, we have to ask a very important set of questions.

    Should I be a Beta Tester?

    • Are you brand new to the project?
    • Do you use the products daily (or close enough)?
    • Do you know how to manually clean up a bad install without losing your data?
    • Do you make regular backups?
    • Do you know the basic troubleshooting steps for the product like the back of your hand?
    • Are you willing to test on your live site?

    If you answered ‘no’ or ‘maybe’ to any of those questions, then you’re not yet ready to be an effective beta tester.

    That last one may catch people by surprise. Testing on your live site is something we actually tell people not to do, but I’m here to tell you that the best beta testers are either testing on their live site or on a site they use every single day. Since I have multiple site I use daily, I picked the one I can live with if it dies and I use that for my live-beta.

    Guess what? You’re on it right now. It’s a hallmark of how much trust I have in WordPress. I can count the issues I’ve had on one hand since I started this a few years ago. I make a backup every day, twice a day, and I have it in three locations. If my site goes down, I lose at most 12 hours of content, and for what I’m doing here, that’s okay. My other sites, I could not deal with that loss and thus I use stable. But I test with live because… well let me explain.

    How do I test?

    If you’re going to be a beta tester, then the you install the beta and you use the product.

    You were expecting rocket science?

    Most projects have a lot of automated tests and they’ll test what they know about and what they can. But absolutely nothing replicates real world use. The whole reason I test with a live site, and why I feel it’s important, is that this is as real as it gets. This site is updated regularly with plugins and theme changes. I add new content five times a week on the same Multisite installation. I add new content multiple times a day on another site. One is on trunk, one is not. This lets me constantly compare the experience between the two and makes me immediately aware that something is different. If it gets my attention, that’s important.

    What I do with my site is equally important. I’m writing content, constantly, which means I’m using some of the fundamental, day to day features that WordPress absolutely must have. That kind of user testing cannot be scripted. There is no AI yet that can reproduce what machinations a human gets up to.

    I like to tell a story about the time my coworker John and I were testing some tax software for the bank. We ran it through it’s paces, putting in data, and every time we tries to save at a certain point, the software crashed. It crashed hard. We called the vendor and talked to them about it, walking them through what we did, and lo, it crashed again. They couldn’t reproduce it, so we screen-recorded it and sent it over. They were astounded and sent out a tech from New York to Chicago to work on this. He couldn’t solve it, but he did see how we crashed it. Then they sent in their big guns, their lead developer, and he sat down and watched us. As soon as it crashed he said he knew what the problem was.

    We were doing it wrong. We were entering bad data in a way no tax professional ever would, the system was trying to process the math and, because the logic was bad, it crashed. I pointed out that people would make mistakes and he agreed, saying it should have alerted us to an error in the form, not try to process anyway. A few days later, he sent us a new version which properly trapped the error.

    While a scripted test might have caught that by looking for bad values before mathing, they were doing the obvious check. They checked if it was a number. They neglected to check if it was a permitted number. If you’ve ever heard me nag someone about proper data sanitization and validation, this is why. They made a code change, added it into their scripted tests, and learned and grew. But they couldn’t have done that without a real human thinking in a different, and unexpected way.

    We can, and will, improve scripted tests, but they will never improve to perfection because humans are pretty crazy. Which leads us to the next step.

    How to I report a bug?

    This is generic for a reason.

    First, make sure you can reproduce the bug. Get a clean build to test on and go through the steps again. If you’re not sure about the steps, make a note to yourself about generally what you remember and try it again. If you can’t reproduce it, call it a one-off and let it go (and debug for yourself, but that’s different). Once you can reproduce it on a clean build, document those steps.

    You’re going to want to answer these questions:

    • What were you doing?
    • What did you expect to happen?
    • What happened?
    • What research did you do into the error?
    • How do you reproduce it reliably?

    Include any error messages. Explain what you did. Don’t say “It didn’t upload.” Instead, say “The upload hung, making no further changes on the page. I waited 15 minutes before hitting refresh. When I checked the media library, I saw the image was uploaded however when I looked at the file server, none of the thumbnails were there.”

    This is why you need to know what you’re testing. With a failed image upload on WordPress, you should know that the image uploads and then it makes the resized images. Even if you don’t know that, you should know your images are in wp-content/uploads/2015/02/ and you should look there to see if you can find them. If you have a failed post, you should know to look at the post list page in WP Admin and see if the post is listed there.

    You’ll get extra bonus points if you can find your PHP error logs and share pertinent information, but that isn’t always easy. When you reproduce the error, make sure you specify if anything special has to happen. Like if the image upload only fails if you’re uploading a PNG, note that. Or maybe it only fails on pages, but not posts. How weird is that, right? Note it.

    Don’t worry about being technical here. Be accurate, be clear spoken, and assume the other person is relatively new at whatever you’ve broken. Don’t assume they know exactly what you mean when you said “Upload an image…” Be specific and say “Create a new post and press the ‘Upload’ button…” If you know there are multiple ways to upload an image, test those other ways. It’s a due diligence thing.

    After you’ve reported the issue, keep informed. Make sure you get email alerts for it, make sure you reply to those emails for it. You can’t just report it and walk away, you have to keep tabs and pay attention. People may need you to clarify information and explain problems in different ways. Remember people can’t actually read your mind. What’s clear to you, because you did it, may confuse them.

    And finally … be prepared to hear that it’s just not that big of a deal. Sometimes a bug isn’t a bug, but an intended change. You may not like it. Heck, you may despise it, but that doesn’t make it wrong.

    What else?

    What are your takeaways from beta testing? How do you do it?

  • Gallery Columns Zero

    Gallery Columns Zero

    I have a site where I love using galleries but I hate having to define their width. That’s something I hate about WordPress’ Gallery shortcode, you have to define a width, otherwise it’s all one column. Ugly ugly.

    The way that WordPress handles these columns also sucks. It puts in clear breaks:

    <br style="clear: both" />
    

    And frankly I hate that too.

    But I don’t do that with this other software I use. In fact, I have it all nicely coded in to show all my images, and then toss one final clear break at the bottom, to … clear the breaks. And what that does for me is gives me an adaptive width gallery that will expand and contract with my content.

    So how can I do that with WordPress?

    The easy part is something I already do in EDD, and that’s to use a fake column value of zero: gallery columns="0"

    That gives me a handy new column class: gallery-columns-0

    And that is very easy for me to style, by overriding the width from 100% to auto (the !important is dreadful), and set up the padding I want.

    /* Gallery */
    
    .gallery-columns-0 dl.gallery-item {
    	width: auto!important;
    	padding: 0;
    	margin: 0 10px 0 0;
    }
    

    But what about the ‘break’ afterwards? If you only need to support IE 8 and up, then it’s as simple as this CSS:

    .gallery-columns-0:after {
    	content: "";
    	display: table;
    	clear: both;
    	padding-bottom: 10px;
    }
    

    The padding on the bottom is to make it match my site, adjust as needed. I’m sure I could use the post_gallery filter hook and the same code from the gallery_shortcode function but with my br modification, but 0.017% of people visit this site using IE 7 or less, and at that percentage, so much of the site will look terrible anyway.

    The only real downside is that I have to manually enter the shortcode in text mode, since I can’t select ‘0’ as an option from the dropdown.

  • Mailbag: Have You Ever Split a Multisite?

    Mailbag: Have You Ever Split a Multisite?

    That was the question.

    Have you ever split a multisite? If so, how?

    I wrote about Breaking Up Multisite before, but this was more specific.

    Yes. And it’s a funny story.

    I should preface the story with the reminder that in general when someone asks me how to do it, I casually mention that they can’t pay me enough to do it. This turned out to be inaccurate, as I was paid to do it. One of my first tasks at DreamHost was to take three separate sites and turn it into a two-site Multisite network. Two blogs were merged into one, then the new site was moved to Multisite. We did that with the export/import tools in WordPress. Fast-forward two years (my how time flies, Simon!) and now I’m asked to un-do it. But they only want site now. The main site is being deleted.

    I was actually glad, since this gave me a chance to handle the site properly and upgrade it correctly. I could clean out the old posts and content, re-sync users, tighten security, and undo the nightmare that was our old process. Plus the exercise of unraveling would give me more experience in WordPress shenanigans. And finally, it answered the question of how much you would have to pay me in order to do this (answer: more than most people would).

    It started out as a massive 30 step process, but after running through it a few times, I was able to speed it up into five, simple, sections. I make use of WP-CLI here, but if you don’t have it you’ll want to get interconnectit’s search and replace tool to save you a migraine.

    Bring it Local

    I use Vagrant and I made example.dev for this.

    Then I just copied down all the files from example.com/wp-content/blogs.dir/2/files/ to example.dev/wp-content/uploads-orig/ and did a database dump. Since I use WP-CLI, this was just a wb db export command.

    That was the full database, though, all 64megs of it, and I only wanted the second site. But we’ll get there in a second. I knew I had WP-CLI on my test box, but if I didn’t, I would have zipped the file in order to use phpMyAdmin (which would make it about 6megs). I’m lazy. I like GUIs. Either way, I imported the entire database to my new server.

    I also made a new wp-config.php file while I was at it, for multiple reasons. The one we were using did a check to see what domain you were on, and loaded different database params based on that. It was a cool bit of code, but it was unnecessary here. Making a new config file is easy (for me), and it ensured I had it clean and only set to a single install of WordPress. After all, I’m de-multisiting.

    Fix the Tables

    Of course, I had to clean that database. The first step was simple and I dropped all wp_FOO tables except wp_users and wp_usermeta. That left me with all the wp_2_ tables.

    Next I renamed wp_2_ to wp_ so I could have everything nice and orderly. But there’s a catch there, becuase there’s an option in my wp_options table that has the name wp_2_user_roles. Can you see what’s wrong? I need that to become wp_user_roles and I need to update any usermeta.

    Break out WP-CLI again and run this: wp search-replace "wp_2_" "wp_"

    So nice. So easy. That actually took care of 100% of the issues with the table renames. Were I doing it manually, it would be time for tears in your beers.

    Clean the Images

    With WP-CLI this is a snap:

    wp search-replace http://example.com/foldername/files/ http://example.dev/wp-content/uploads/
    wp search-replace http://example.com/foldername http://example.dev
    wp search-replace wp-content/blogs.dir/2/files wp-content/uploads
    

    I ran it like that for a reason. I like to do my searches in order of smallest catch to biggest, and this way it kept my possible gaffs to a minimum. I knew I had to fix all the images and post content, so it was safer this way.

    Clean up Users, Themes, and Plugins

    We had a lot of old, duplicate, users who had no posts or had left the project. I went over everyone’s permissions, dropped them down as low as I could, and removed half the admins. It’s just a good time for that.

    Next I reinstalled themes and plugins. I could have just copied them down, but I reinstalled everything because I wanted to take the time to make sure they were all clean and the latest versions. This is also where I paused to do a security review of everything we had.

    Move it Live

    Well now we’re just moving WordPress like normal. Copy it all up via FTP, copy up the database, run a last search replace to change example.dev to the real, new, domain (which I don’t actually know yet know), and it’s done. If I use wp-cli again, this will be as simple as running this: wp search-replace example.dev newsite.com

    All that extra work I did before pays off here.

    The nice thing about this is that I could have done this and then keep the main site if I’d wanted to. I didn’t, but I could have easily deleted all the wp_2_ tables and just cleaned up the multisite stuff. The headache is I’d have to do this multiple times if I’d had, say, ten sites on the network and wanted to move them all. If that had been the case, I would have only exported the wp_2_ tables and the wp_users and wp_usermeta ones.

    But yes. I have un-multi’d a site.

  • 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.