Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Prettier DreamObjects Listing

    Prettier DreamObjects Listing

    A long time ago, I wrote about how to list your objects on DreamObjects. This code still works. I’m using it today, and as long as my bucket is public, it’s perfect.

    It’s also kind of ugly and a hassle to get the link. I have to right-click every time, and really I just want to grab the link, paste it somewhere else, and go.

    Thankfully it’s 2016 and there’s a library called clipboard.js that does this.

    Before I did that, I decided how I wanted the layout to be. Two buttons, one for a link copy and one for a link to open in a new window.

    The button list

    Looks pretty basic, I know, but it’s better than that list.

    Next I took my foreach loop and made it this:

    	foreach ($bucket_contents as $file){
    	
    	    $fname = $file['name'];
    	    $fname_pretty = strtolower( substr( $file['name'] , 0, strrpos( $file['name'] , ".") ) );
    	    $furl = "http://".$bucketName.".objects.dreamhost.com/".$fname;
    	
    	    echo '<div id="example-text" class="example">';
            echo '<button class="btn" data-clipboard-demo data-clipboard-action="copy" data-clipboard-text="'.$furl.'" aria-true="Copied!">Copy link to '.$fname_pretty.'</button> ';
            echo ' <a class="btn" href="'.$furl.'" target="new">Open link to '.$fname_pretty.'</a>';
            echo '</div>';
    	}
    

    This gave me the output and set me up for the script code at the bottom of the page:

        <script src="clipboard.min.js"></script>
        <script src="tooltips.js"></script>
        <script>
        var btns = document.querySelectorAll('button');
        var clipboard = new Clipboard(btns);
        clipboard.on('success', function(e) {
            console.log(e);
            showTooltip(e.trigger, 'Copied!');
        });
        clipboard.on('error', function(e) {
            console.log(e);
        });
        </script>
    

    Obviously clipboard.min.js is what you think it is, but the tooltips is so you can have that nice showTooltip popup so you know what you just did.

    When you combine that with the work I did the other day for a droplet, all I have to do is keep that page up and handy to throw links at people.

    You can see it at https://ipstenu.org/dreamobjects/.

    Before you ask, since not everything there is an image (‘Wrong’ is Dom Deluise shouting that), and since the images are all varying sizes, I went with not showing the image at all.

  • 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