Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • DreamObjects Droplet

    DreamObjects Droplet

    I have DreamObjects, I have Transmit, and I have gifs I like to throw at people. I’d been using a tool called CloudUp, which is cool, but it had been broken for a while and was bothering me, so what I really wanted was a way to save my images to DreamObjects. It’s dogfooding, right?

    Add To Transmit

    I like Transmit. I have the desktop app and I’ve used it for years. They do it right. Early on they added S3 to their app, so adding DreamObjects to it is so simple it’s painless. We have cool directions on the DreamHost KB about how to do it.

    Make a Droplet

    Once you’re connected, you need to make a droplet. I happen to have a bucket called ipstenu-images which I use for all my images. I know, it’s totally imaginative. Next I hit right-click and selected ‘Save Droplet For Folder’. It looks like this:

    Select the folder you want and right-click. Pick Make a droplet

    I chose to save mine in the ‘favorites’ folder, which I sync on Dropbox. I did choose to save the password information as well, which if you’re doing this on your own computer is relatively safe.

    When it saved, it opened up a Finder window with my new Droplet, named “DreamObjects droplet.” I dragged that to my taskbar and tested it by dropping an image.

    nope

    That’s my default test image.

    Set Permissions Properly

    Except it didn’t work!

    Permissions Error

    The permissions error was because the upload defaulted to only letting the owner see the image. Thankfully this is an easy preference setting in Transmit. Go into Preferences, then Rules, and at the bottom you have permissions. Click on the S3 tab and change “Read” from “Owner” to “World.”

    Change S3 Defaults to READ: WORLD

    Now anyone can go to https://ipstenu-images.objects.dreamhost.com/nope.gif and see a nope-ta-pus!

     

  • On The Fly Checklists

    On The Fly Checklists

    When you do the same thing over and over, the best thing to do is automate it. We all know that.

    However there are simply some things you cannot automate. As horrible as that is to say in today’s world, you cannot automate things like “Proofread content for errors.” Why? Well a computer only knows to look for what you tell it to look for. And if you habitually typo homophones, you’re in for a long day.

    That’s why we have things like checklists. Do X, check Y, etc etc. They’re there to ensure when we run the automated steps, we do everything properly.

    I spent the first half of April generating Pre-Flight Checklists for a lot of things. How to upgrade things at DreamHost, how to update things at WordPress.org, how to migrate from X to Y, and so on and so forth. The sad thing is that I’ve had to do all these for long standing processes which exist mostly in someone’s head.

    Here are some tips to how I do it.

    Write Your Checklist As You Do It

    If it’s a process you know, have a document open (or a piece of paper) and write as you go, enumerating every single step. Be pedantic.

    On Your Computer:

    1. Generate list of X
      1. Go to Y and click BUTTON
    2. Copy list to DOC
    3. Run TOOL to output new file
    4. Add new file to repository
      1. $ git commit -a FILENAME
      2. $ git push

    On Remote Server:

    1. SSH to 123.45.67.89 as your ID
    2. Go to folder BAR
      1. cd /home/blah/foo/bar
    3. Dryrun sync command
      1. sync bar baz –dry-run

    You get the idea. Like I said, be specific and get every single thing you do. Don’t worry about getting everything perfect. This is about getting all the information.

    Walk Through Your Checklist

    Once you have it up, walk through it and do everything (except the actual code push parts). This will show you how you can clean it up and what needs to be explained better. I find it helpful to ask myself “If I was new, would I know what ‘the remote server’ is?” If the answer is no (which it often is), I become more pedantic and specify what server to connect to and exactly how.

    For example instead of “Connect to Servername” I’ll put in this:

    • Connect to production server
      • ssh servername — use your ID and password

    Using the different font helps me to know it’s a command and not directions. Any time you feel something is vague, explain it. Put in examples of output. Don’t be afraid to break the ‘flow’ of a checklist to ensure clarity.

    Ask Someone Who Knows to Proof

    If this is a process someone else has done, grab them and have them skim it for anything obviously wrong. Often I’m writing and refining a checklist for something I don’t actually do, but I watch regularly. With that mindset, I’m able to write from a place where I know enough to know what has to be clear to someone new. A place of no assumptions.

    That person should question your claims. if you say “Check X” and they come back and ask you “Why are you doing X in step 15? You do the work in step 4. Why not do them together?” That’s a good thing! You want to be clear, but if there’s work that’s duplicated, you can save yourself and the future-you time. Also that person is going to be the one who says “We should explain why we do this…” They’re going to teach you more about the process.

    Ask Someone Who Doesn’t Know to Proof

    This is harder. You need someone who knows ‘enough’ that you’re not explaining what SSH is, but doesn’t automatically make assumption. This may be why I end up on checklists a lot. I pestered the hell out of my coworker, Mike, and asked him to explain things in broad terms and then nitty gritty. I asked him to walk me through the steps, then I wrote them down and turned around to someone else and asked “Does this make sense?”

    Why? Because Mike was too close to it to see the forest for the trees, which is a good thing! He knows it all! I was fairly close to it, enough to possibly get in over my head. By grabbing a total novice, we had the trifecta of brilliance. That third person asked “What is this?” and noted multiple things that might be wrong.

    Release and Iterate

    You’re going to miss things, so every time you do the process, have the red pen ready.

  • Combining Data from Multiple CPTs

    Combining Data from Multiple CPTs

    I wanted to get a list of all TV shows where 100% of the listed characters were dead.

    @Ipstenu … Did I just read that you’re using WordPress to compose a list of dead lesbians in media? I have to say, that’s kind of unique.
    Otto42

    Yes, yes I am. Television media, excluding reality TV.

    The problem is I store the information in two Custom Post Types (shows and characters). And while both shows and characters have a way to track if there are dead, getting that list was frustratingly complicated.

    We wanted, originally, a way to mark a show as having death and a separate way to track each dead character. Since they were separate CPTs, we made two custom taxonomies: dead-character and death. Logically then, I could use WP Query to get a list of all shows with the cliche taxonomy field of ‘death’ checked:

    $dead_shows_query = new WP_Query ( array(
    	'post_type'       => 'post_type_shows',
    	'posts_per_page'  => -1,
    	'post_status'     => array( 'publish', 'draft' ),
    	'tax_query' => array(
    		array(
    			'taxonomy' => 'cliches',
    			'field'    => 'slug',
    			'terms'    => 'death',
    			),
    		),
    	)
    );
    

    I know I could use a different way to get terms, but I don’t want to since I need to count these shows:

    $count_dead_shows = $dead_shows_query->post_count;
    

    But I also need to continue processing. Once I have a list of shows with death, I need to get a list of all characters who are on the show. That data is stored as Custom Meta Data and those don’t have a quick and easy sort method. Worse, the shows are listed as an array.

    Originally it was just a number representing the post ID for the show, and that was pretty easy to check. While we have posts in the dead show query, get a list of all characters with this ‘show ID’ which looks like this:

    	while ( $dead_shows_query->have_posts() ) {
    		$dead_shows_query->the_post();
    		$show_id = $post->ID;
    
    		$character_death_loop = new WP_Query( array(
    			'post_type'       => 'post_type_characters',
    			'post_status'     => array( 'publish', 'draft' ),
    			'orderby'         => 'title',
    			'order'           => 'ASC',
    			'posts_per_page'  => '-1',
    			'meta_query'      => array( array(
    				'key'     => 'the_show',
    				'value'   => $show_id,
    				'compare' => '=',
    			),),
    		) );
    
    		if ($character_death_loop->have_posts() ) {
    			[... do all the magic here ...]
    			wp_reset_query();
    		}
    	}
    

    The problem is that compare line. Once you stop looking for “Does 123 == 123” you have to use ‘IN’ or ‘LIKE’ and, since this is an array of show IDs, we need 'compare' => 'LIKE' for this check. That sounds simple, but there’s one more small problem. If the show ID is 1, then every single show with a 1 in it shows up. In addition, I actually wanted to get a list of all the shows where some characters died, so I couldn’t just check for characters with the meta_query of the show and the tax_query of death.

    My first step in all this was to convert the shows to an array:

    if ( !is_array (get_post_meta($post->ID, 'lezchars_show', true)) ) {
    	$shows_array = array( get_post_meta($post->ID, 'lezchars_show', true) );
    } else {
    	$shows_array = get_post_meta($post->ID, 'lezchars_show', true);
    }
    

    Now I can process everything as an array and check if the show ID is really in the array. If it is, we’re going to record that there is a character in a show with death.

    if ( in_array( $show_id, $shows_array ) ) {
    	$chardeathcount++;
    }
    

    Next we’re going to check if the character has the tag ‘dead-character’ (listed as a custom taxonomy for ‘show cliches’) or not and is in the show:

    if ( has_term( 'dead-character', 'cliches', $post->ID ) && in_array( $show_id, $shows_array ) ) {
    	$fulldeathcount++;
    }
    

    Once we have that data, I need two arrays. The first is for shows where the count of all characters on the show is the same as the ones who are dead. The second is for shows where some characters are dead:

    if ( $fulldeathcount == $chardeathcount ) {
    	$fullshow_death_array[$show_id] = array(
    		'url'    => get_permalink( $show_id ),
    		'name'   => get_the_title( $show_id ),
    		'status' => get_post_status( $show_id ),
    	);
    } elseif ( $fulldeathcount > '0' && $fulldeathcount <= $chardeathcount ) {
    	$someshow_death_array[$show_id] = array(
    		'url'    => get_permalink( $show_id ),
    		'name'   => get_the_title( $show_id ),
    		'status' => get_post_status( $show_id ),
    	);
    

    The full code for that magical snippage looks like this:

    			$fulldeathcount = 0;
    			$chardeathcount = 0;
    
    			while ($character_death_loop->have_posts()) {
    				$character_death_loop->the_post();
    
    				if ( !is_array (get_post_meta($post->ID, 'lezchars_show', true)) ) {
    					$shows_array = array( get_post_meta($post->ID, 'lezchars_show', true) );
    				} else {
    					$shows_array = get_post_meta($post->ID, 'lezchars_show', true);
    				}
    
    				if ( in_array( $show_id, $shows_array ) ) {
    					$chardeathcount++;
    				}
    
    				if ( has_term( 'dead', 'tropes', $post->ID ) && in_array( $show_id, $shows_array ) ) {
    					$fulldeathcount++;
    				}
    			}
    
    			if ( $fulldeathcount == $chardeathcount ) {
    				$fullshow_death_array[$show_id] = array(
    					'url'    => get_permalink( $show_id ),
    					'name'   => get_the_title( $show_id ),
    					'status' => get_post_status( $show_id ),
    				);
    			} elseif ( $fulldeathcount >= '1' && $fulldeathcount <= $chardeathcount ) {
    				$someshow_death_array[$show_id] = array(
    					'url'    => get_permalink( $show_id ),
    					'name'   => get_the_title( $show_id ),
    					'status' => get_post_status( $show_id ),
    				);
    			}
    

    And yes, it works just fine.

  • More Chart.js Fun

    More Chart.js Fun

    The statistics page on my TV database site is pretty fun. It’s the one I learned how to use Chart.js for in the first place.

    I wanted to add in some pie charts, but first I upgraded the code to the 2.0-beta and refined the PHP on my stats. Originally I just had some pretty basic bar charts for category statistics. Now I have some pie charts to show characters by role (by which I mean are they a main character, a guest, or a recurring character).

    Better Category Stats

    Let’s talk about some better PHP first. The stuff I had before worked, but it could have been better. Here’s the better PHP:

    $count_characters = wp_count_posts( 'post_type_characters' )->publish + wp_count_posts( 'post_type_characters' )->draft ;
    $tropes = get_terms('chartags');
    
    $trope_terms_array = array();
    foreach ( $tropes as $trope ) {
    	$trope_terms_array[$trope->slug] = array( 'count' => $trope->count, 'name'  => $trope->name, 'url' => get_term_link( $trope ) );
    }
    

    What I did here was take the data and make a single array for it which gives me the data structured as follows:

    Array
    (
        [addict] => Array
            (
                [count] => 2
                [name] => Addict
                [url] => http://example.com/tropes/addict/
            )
    
        [athlete] => Array
            (
                [count] => 3
                [name] => Athlete
                [url] => http://example.com/tropes/athlete/
            )
    )
    

    This makes my array much smaller and simpler to run through. Next I changed how I call the data in my javascript:

    	labels : [<?php
    		$unset = array('foo','bar','baz');
    		foreach ( $unset as $item ) {
    			unset($trope_terms_array[$item]);
    		}
    
    		foreach ( $trope_terms_array as $trope ) {
    			echo '"'.$trope['name'].'", ';
    		}
    	?>],
    

    The unsetting at the top is a quick run to remove the tropes I don’t need for this chart because I’m displaying them in the pie chart. See? It all comes together!

    Pie No. 1 – Sexuality

    There are two pie charts. One is checking how many characters are gay, straight, or bisexual. I’m sure eventually I’ll be add asexual, but that isn’t today. Anyway, that chart is surprisingly simple. Since I’d already improved the PHP call for category stats, and sexuality is saved as a character taxonomy, I was able to do this simply as follows:

    <script>
    // Piechart for sexuality stats
    var pieSexdata = {
        labels: [
            "Gay",
            "Straight",
            "Bisexual"
        ],
        datasets: [
            {
                data: [ <?php echo '
    	            "'.( $count_characters - $trope_terms_array['straight']['count'] - $trope_terms_array['bisexual']['count'] ).'",
    	            "'.$trope_terms_array['straight']['count'].'",
    	            "'.$trope_terms_array['bisexual']['count'].'"
    	            '; ?>],
                backgroundColor: [
                    "#7d3255",
                    "#327A7D",
                    "#32557D"
                ],
                hoverBackgroundColor: [
                    "#B18499",
                    "#BCD4D5",
                    "#ABB9CA"
                ]
            }]
    };
    
    var ctx = document.getElementById("pieSex").getContext("2d");
    var pieSex = new Chart(ctx,{
        type:'doughnut',
        data: pieSexdata,
        options: {}
    });
    </script>
    

    The default assumption is that any character being added is a homosexual. The reason ‘straight’ is there is for a character who was presented as gay, but that turned out to be a fantasy sequence. Thanks, Roseanne. With that in mind, calculating the number of gay characters was a matter of subtracting the straight and bisexual. And yes, I named the chart pieSexdata on purpose.

    Pie No. 2 – Character Role

    The second pie chart was a lot harder. You see, I’d chosen to save the ‘role’ as a custom meta field in the post. There’s a dropdown for ‘Main’ or ‘Recurring’ or ‘Guest’ and it defaults to ‘None’ if you don’t fill it out. Right now everyone has a role but I coded in a failsafe.

    This code took me a while to sort out, but as soon as I realized how simple it was, I made a loop so I didn’t have to repeat code:

    $roles = array( 'regular', 'recurring', 'guest' );
    $roles_array = array();
    foreach ( $roles as $role ) {
    	$args = array(
    		'post_type'       => 'post_type_characters',
    		'posts_per_page'  => -1,
    		'post_status'     => array( 'publish', 'draft' ),
    		'meta_query'      => array(
    			array(
    				'key'     => 'chars_type',
    				'value'   => $role,
    				'compare' => '=',
    			),
    		),
        );
    	$thisrole = new WP_Query($args);
    	$roles_array[$role] = $thisrole->post_count;
    }
    

    This produces a nice array:

    Array
    (
        [regular] => 147
        [recurring] => 37
        [guest] => 23
    )
    

    I wanted it to be an array since I can see this expanding sooner or later. The pie chart code looks very much the same as the one for sexuality, and all that’s really different is how I’m calling the data and doing the math for how many characters have no listed role.

        labels: [
            "Main Character",
            "Recurring Character",
            "Guest Character",
            "No Role"
        ],
        datasets: [
            {
                data: [ <?php echo '
    	            "'.$roles_array['regular'].'",
    	            "'.$roles_array['recurring'].'",
    	            "'.$roles_array['guest'].'",
    	            "'.( $count_characters - $roles_array['guest'] - $roles_array['recurring'] - $roles_array['regular'] ).'",
    	            '; ?>],
    

    What’s Next?

    Things are shaping up nicely, but I want to add in better labels. I’d like if they show the percentage when you hover over them on pie charts, and if they could link to the taxonomy pages for the bar charts. But I haven’t quite sorted out how to do that yet.

    I also have to blame Tracy for this, because she’s the one who wanted stats like that in the first place.

  • Fauxgo and Rickroll

    Fauxgo and Rickroll

    April Fools is today. I hate April Fools ‘jokes’ as pretty much all of them are cruel.

    Now that we have that out of the way, I’d like to tell you about two plugins I created that are pretty much useless but educational.

    Rickroll

    Released in the WordPress.org plugin directory, this plugin changes all your videos to the official Rickroll video. All. Your. Videos. The point it was made for is that you really can run a filter to do some pretty impressive things with videos. Including replace them. You can take the logic of this plugin and apply it by filtering (say) all videos embedded in comments made by a specific person. That would really mess with them. Or you could possibly call YouTube’s api, check the rating of the video, and if it’s over a certain amount, show a default instead.

    Download: WordPress.org – Rickroll

    Fauxgo

    This plugin replaces the WordPress logo with a Fauxgo. Why? Someone complained to me that you couldn’t rebrand WordPress entirely. So I did. The thing about this plugin was that it was deucedly complicated to make work right. Most of the trouble was the CSS is ‘weird.’ But once you have it installed, everything looks different ‘ish’ and suddenly you’re not sure what the logo is anymore.

    I plan to edit this and write it as using an SVG icon instead of the icon font.

    Download: Github – Fauxgo

  • Migrating from CMB to CMB2

    Migrating from CMB to CMB2

    I didn’t plan to, the first time. I’d inherited a site that I offered to help someone clean up and they had CMB2. They didn’t actually. They had CMB, the original. This was a while ago. Looking at their site, I realized they had one (yes, one) custom meta box, so I removed CMB and coded in that one meta box and called it a day.

    Flash forward a while. A long while. I have a site that was mostly built out by someone else, and it worked great except on mobile. After trying to update content on the site on my iPad and getting frustrated to the point of angor, I re-did the theme as Metro Pro (yes, it’s that site), and folded in a lot of the meta boxes into mu-plugins, so we could keep them no matter what the theme.

    But again, she’d used CMB. Not CMB2. And since I know CMB2 has a lot more features, I decided to upgrade. Three hours later, I had it done and had it done rather nicely.

    Decide how to install CMB2

    I did it as an mu-plugin – Look. It’s a library. I have my font library (aaah!) in there as well. This is how I organize things. I don’t want people disabling it on accident, so by having it in my Must Use folder, only people with SSH or Git access can screw with it. This is a protection thing.

    I tossed the cmb2 folder in there and whipped up a fast cmb2.php bootstrap file:

    if ( file_exists( dirname( __FILE__ ) . '/cmb2/init.php' ) ) {
    	require_once dirname( __FILE__ ) . '/cmb2/init.php';
    } elseif ( file_exists( dirname( __FILE__ ) . '/CMB2/init.php' ) ) {
    	require_once dirname( __FILE__ ) . '/CMB2/init.php';
    }
    
    // Extra Get post options.
    function cmb2_get_post_options( $query_args ) {
        $args = wp_parse_args( $query_args, array(
            'post_type'   => 'post',
            'numberposts' => -1,
        ) );
        $posts = get_posts( $args );
        $post_options = array();
        if ( $posts ) {
            foreach ( $posts as $post ) {
              $post_options[ $post->ID ] = $post->post_title;
            }
        }
        asort($post_options);
        return $post_options;
    }
    
    // Handle the CSS for this
    function cmb2_site_scripts( $hook ) {
    	if ( $hook == 'post.php' || $hook == 'post-new.php' || $hook == 'page-new.php' || $hook == 'page.php' ) {
    		wp_register_style( 'cmb-styles', plugins_url('/cmb2.css', __FILE__ ) );
    		wp_enqueue_style( 'cmb-styles' );
    	}
    }
    add_action( 'admin_enqueue_scripts', 'cmb2_site_scripts', 10 );
    

    You’ll notice that’s more than just bootstrapping. This is the other reason I wanted it as an MU plugin. The first part with the require_once is the call for CMB2. I could simplify it since the folder is lowercase, but it’s fine as is. The second part with function cmb2_get_post_options is so that I can use the names of all my posts as options in a dropdown. I didn’t invent this code, it’s from the CMB2 documentation. The last bit of function cmb2_site_scripts is just to make sure my custom CSS gets loaded on the right pages (I wanted to change the layout of some things).

    Convert the Calls

    This was the weird part. I’d never really used CMB before. CMB2, through various things including reviews, I’m pretty familiar with the general aspects of it, though not all the specific calls. From CMB to CMB2, the major change was that instead of multiple nested arrays with a return, the code was wrapped in a function that had an array, but then it had callbacks.

    This:

    	$meta_boxes[] = array(
    		'id'         => 'chars_metabox',
    		'title'      => 'Character Details',
    		'pages'      => array( 'post_type_characters', ), // Post type
    		'context'    => 'normal',
    		'priority'   => 'high',
    		'show_names' => true, // Character field names on the left
    		'fields'     => array( ... )
    	);
    
    	return $meta_boxes;
    

    became this:

    	$cmb_characters = new_cmb2_box( array(
    		'id'            => 'chars_metabox',
    		'title'         => 'Character Details',
    		'object_types'  => array( 'post_type_characters', ), // Post type
    		'context'       => 'normal',
    		'priority'      => 'high',
    		'show_names   ' => true, // Show field names on the left
    	) );
    

    You can totally see how one transmuted into the other, right?

    The biggest change is the 'fields' => array( ... ) section, which is totally missing from the new version. Remaking the fields was trickier since some changed a great deal. And some changed in fantastic ways.

    Here’s the original actor name field:

    			array(
    				'name' => 'Actor Name',
    				'id'   => $prefix . 'actor',
    				'type' => 'text',
    			),
    

    And here is the new one:

    	$cmb_characters->add_field( array(
    		'name'       => 'Actor Name',
    		'desc'       => 'Include years (in parens) for multiple actors',
    		'id'         => $prefix . 'actor',
    		'type'       => 'text',
    		'repeatable' => 'true',
    	) );
    

    I’ve added in two things. First is the desc (description), which was needed because I was adding in the repeatable field! Sometimes actors are replaced, and with that in mind we’d been using ‘Foo (2013), Bar (2014-2015)’ and so on. But now with repeatable fields we could easily add in a new line for every actor. Problem solved!

    Why not hand code?

    Because the Fields API is a sack of wet, smelly, rotting, hair. It’s actually worse than the Settings API. I can’t wait for the Fields API plugin to hit release candidate and have a UI built in. Until then, hand coding more than one meta box is a headache. Making three groups with three to eight fields in each with cross dependencies? A nightmare.

    Simply put, CMB2 does it well, obviously, and simply. The code is easy to understand and implement. I wish it was in core. It’s around 3 megs, but 2 of them are from translations, so it’s really not as horrible as all that.