Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

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

  • Null and Zero

    Null and Zero

    This is an amusing anecdote.

    When I was working on my cross-check of shows and characters, I got everything working right except one thing. I noticed my count of ‘shows with death’ made a weird pie chart. You see, the number of shows was off by one compared to the total number of shows. I went back and forth, trying to figure out why it was doing that, and in the end I decided that I’d output the list and manually count to see what I was missing.

    I counted, by hand, the shows that I got with the list of ‘all characters are dead’ and that number matched what the code output. Then I subtracted that from the number of ‘shows with any death’ to get the ‘some characters are dead’ count, and that too was okay. This meant that, for some reason, the list of shows where no one died was wrong. But it was subtraction! How could it be wrong!

    Well as it turns out, I forgot that null and zero aren’t the same to a computer, but they tend to be to a human’s brain.

    Now I know this! I know that zero is a number, it’s a value of the known quantity of zero. And I know that ‘null’ is a non value, meaning that it’s a data point that cannot yet be known.

    You can’t math on null. And I knew this. Earlier in my code I’d written $foo = 0 outside of my loop where I check and, as needed, increment it with $foo++ specifically because you can’t add to null.

    Nothing from nothing, carry the nothing…

    But. In my calculations, I checked the following:

    1. If a show has been flagged with ‘death,’ count all the characters.
    2. For each character, if they’re dead, add 1 to the death count.
    3. If the number of characters is the same as the death count, add this show to the list of ‘kill ’em all.’
    4. If the number of characters is greater than the number of dead, and the value of either isn’t 0, add the show to the list of ‘kill some.’

    Then, separately, I checked “If the show has not been flagged with death, add the show to the ‘kill none’ list, because that’s a 1/0 check. And when you don’t think too deeply about that, it sounds fine, right? If they aren’t marked with death they must kill none. And if they don’t kill them all, and they don’t kill some, then the number should be the same as the ‘kill none’ list.

    The problem was that I had one show, just one, where all the characters were alive but it had been flagged with death. This was not an error. The show did kill off a character, but I’d not added the dead character yet. Which meant it ran through the checks like this:

    1. Flagged with death, we count all the characters (8).
    2. If the character is dead, add one to death count (death count is 0).
    3. If the number of characters (8) is the same as the dead (0), add to ‘kill them all’ (false).
    4. If the number of characters (8) is more than the dead (0) (true), add the value of either isn’t 0 (false), add them to ‘kill some.’ (false).

    Which isn’t wrong at all. It just meant that the show didn’t get listed on ‘kill none’ because it was flagged with dead, and it didn’t get listed on ‘kill them all’ because 8 is more than 0, and it didn’t get listed on ‘kill some’ because while 8 is more than 0, dead was 0.

    Oh silly me. The value of death characters was ‘null.’ They simply didn’t exist in a place where they should have.

    The fix? I added the dead character to the site and everything was fixed. But code wise, could I prevent this? I certainly should, since I know better than many that you can’t predict what users are going to do. So it begs the question of why was I checking if character count and dead count were not 0 to begin with? It was, originally, because my ‘no deaths’ list was screwed up and I threw that check in to omit cases where a show was flagged as death but didn’t have death.

    The accuracy of these things depends entirely on your data. Garbage in, garbage out. The best fix would be to check ‘if a show has death and it has no dead characters, adjust the dead count down by one’ but also ‘if a show has no death, but it has dead characters, adjust the live show count down by one.’ I haven’t done that yet. I will soon.

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

  • Because Developers Never Typo

    Because Developers Never Typo

    It’s not a problem. Only admins can use this.

    I’d pushed back on a plugin that wasn’t validating their post input wisely. Instead they just slapped sanitize_text_field() around everything and called it a day.

    One of the myriad reasons I’ll push back on a plugin is improper sanitization. When I say that, I mean they need to sanitize, validate, and escape the input. If I see things like update_option('my_cool_options', $_POST['my_cool_input']); I’ll push back and tell them to please sanitize. But really I tell them this:

    SANITIZE: All instances where generated content is inserted into the database, or into a file, or being otherwise processed by WordPress, the data MUST be properly sanitized for security. By sanitizing your POST data when used to make action calls or URL redirects, you will lessen the possibility of XSS vulnerabilities. You should never have a raw data inserted into the database, even by a update function, and even with a prepare() call.

    VALIDATE: In addition to sanitization, you should validate all your calls. If a $_POST call should only be a number, ensure it’s an int() before you pass it through anything. Even if you’re sanitizing or using WordPress functions to ensure things are safe, we ask you please validate for sanity’s sake. Any time you are adding data to the database, it should be the right data.

    ESCAPE: Similarly, when you’re outputting data, make sure to escape it properly, so it can’t hijack admin screens. There are many esc_*() functions you can use to make sure you don’t show people the wrong data.

    I say it often. Sanitize everything (but especially what you save or process), validate input, escape output.

    I understand though, why someone might naively assume that since only admins can do a thing, it’s ‘safer.’ The truth is admins screw up as much as anyone else. Worse, probably, since admins have more power and often think they know better.

    But the point I was trying to make to this guy was that it doesn’t matter who is inputting the data.

    I’ve told this story before. I used to have a job testing software packages for software I didn’t use. We replied on ‘scripts’ from people to know what to test. One day, John C and I were trying to test some new software and every time we hit a certain screen, we’d crash the box. We tried over and over and it failed. So John called the vendor and explained what we were doing. They walked us through it and it crashed. Since we were a VIP, they said they’d send over a couple developers. When the two guys showed up, one watched us very carefully and was shocked.

    The young dev exclaimed, “Why would you ever input wrong data there?!”

    I eyeballed him. “Why would putting in wrong data crash the computer?”

    The older dev chuckled. “We’ll put in an error check there.”

    The lesson I learned is simple. Never trust input.

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

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