Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

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

  • Composer and WordPress’ Plugin Preflight Check

    Composer and WordPress’ Plugin Preflight Check

    When you upload a plugin to WordPress.org’s repository, it does some pre-flight checks to make sure the code is okay. More than once it’s caught a missing ; for me. But one day it caught this:

    PHP error in: myplugin/tags/0.6/aws/Doctrine/Common/Proxy/ProxyGenerator.php:
    Errors parsing myplugin/tags/0.6/aws/Doctrine/Common/Proxy/ProxyGenerator.php
    

    I stared for a moment, Googled to be sure, and sighed when I figured out that what was happening was my library was a PHP 5.6+ version and that didn’t pass the 5.4 checks.

    Regardless of how annoying this is, it’s not too hard to fix. No, I’m not going to tell you how to avoid the scan, nor am I going to talk about the stupidity of the scan. Instead I’d like to tell you how to get around this problem.

    It’s so easy, too.

    You see, my issue is that I use Composer to build my resource libraries and in my composer.json file, I have this:

    "require": {
    	"aws/aws-sdk-php": "2.7.*",
    	"doctrine/orm": "*",
    	"monolog/monolog": "*"
    },
    

    And for what it’s worth, this is great. I have it download all my libraries. Then I have it run a script to copy over just what I need to my aws folder and I have Git and SVN ignore the vendor folder. Everyone wins! The problem is that Composer sees that my laptop is running PHP 5.6 and so it installed version

    Fix PHP on Your Computer

    Since I use Homebrew on my Mac, this is super easy for me.

    $ brew unlink php56
    $ brew install php54
    

    Then when I re-run composer update it will install what I need.

    The problem here, though, is that this is tied to a specific computer, and if a coworker downloads my stuff and plans to fork it, they’re SOL and merges fail and it’s sad times all around. So with that in mind, uninstall 5.4 and go back to 5.6.

    If you have a problem with installing 5.6, run it this way:

    $ xcode-select --install
    $ brew unlink php54
    $ brew install php56
    

    Fix PHP in Composer

    I like this fix better. Put this above your require statement and you’ll force Composer to build based on PHP 5.4:

    "config": {
    		"platform": {
    		"php": "5.4"
    	 }
    },
    

    Now we’re cooking with fire. This works for everyone, it forces Composer to download 5.4 compatible libraries, and my very long transmission worked.

    Is This Perfect?

    Obviously not. There are going to be cases where your code absolutely has to be the code for PHP 5.5 and up. The latest version of the AWS SDK is v3, for example. But it’s for 5.5 only. I personally don’t feel there’s enough of a buy-in for PHP 5.5 and up yet to make an issue of it. And having no check at all won’t fly.

  • The Trouble With Libraries

    The Trouble With Libraries

    I’ve had the same argument over and over, to the point that people follow me here and complain that I suck. You’re welcome. I’m going to spell out the issues, as I personally see them with frameworks as plugins. If you want to talk about this in so far as it applies to the WordPress plugin repository, please read this post and keep the discussion there.

    The Problems

    Problem 1 – Users have no idea what the ‘library’ plugin is for.

    Most users understand “I have an add-on for WooCommerce, I probably need Woo.” They do not always understand “I have plugin Slider Joe. Why do I need Advanced Custom Fields?”

    Problem 2 – We don’t have true ‘dependancies’ in WordPress

    I think everyone can accept that’s a problem. We literally do not have a perfect way to require things. Look at Themes. If you try to delete a parent theme, you get warned there are children around. We don’t have that for plugins. We probably should.

    Problem 3 – Users are responsible for something they don’t understand.

    By having a library as a plugin, the onus of version compatibility and updates is now on the person least likely to understand it when it breaks: the user. They have to update the library, and your plugins, every time there’s a new version.

    Problem 4 – Frameworks and libraries can no longer break backwards compatibility.

    This is hugely restrictive, by the way. With a framework-as-plugin you can’t break things because you (the framework developer) are responsible for all the plugins that use your framework. If you break how things work from V1 to V2, and one of the myriad plugins a user has doesn’t work on V2, and the user updates your framework, you broke things. True, this was always the case, but at least the plugin contained the library and could use it’s own version.

    Problem 5 – Plugins will still break.

    I have to say ‘still’ because we have one version of the problem today, and we’ll have another tomorrow. Right now, if four plugins include the same library, and they’re all different versions, we don’t have a clear and perfect way to know which version of the library the user will get. Tomorrow, if a framework is a separate plugin, there’s absolutely no assurance than every plugin that requires that library has been tested with the version the user has install.

    The Options

    Today we really have two.

    Option 1 – Frameworks are plugins and users have to install them.

    This means all new plugins that include said framework have to remove it and change it to a require. All existing plugins should be contacted and told to change their code. Some users will complain about installing ‘extra’ plugins and some developers will lose users (it’s already happened).

    All developers have to put in this requirement themselves, possibly using a library like TGM (until we have dependancies). Also all developers have to ensure they, now and forever, keep up with the frameworks and ensure compatibility as well as proper alerts if a user removes the framework by accident. Their code has to break elegantly if the user doesn’t upgrade the library. Your plugin takes advantage of the latest feature in a framework? Awesome. Make sure your plugin checks “If this feature exists, use it” and fails gracefully if not.

    Option 2 – Frameworks that are not ‘functional’ frameworks, but really libraries are treated as all libraries are with all projects, and included separately.

    Developers have to code the calls to the library, making sure that the ‘right’ version is included no matter what someone else includes. Developers also have to update their plugins when a library updates. though if they properly handle the code calls, they don’t HAVE to. They could use namespaces and cleverly call use MYPLUGINAWSSDK as /aws/AWS/foo/bar instead, so their version is what’s used. They’ll probably want to code in a failsafe “If a version higher than mine is used, show a warning.”

    The Solution

    Looking at the options we have today, we have to ask “Which is better?”

    Neither. They both suck for developers. They both suck for the users. They both frustrate everyone. I have heard arguments from the same number of developers for each option. Some developers want to include the ‘core’ or a framework in their plugin because it’s ‘better’ than requiring another plugin. Other developers want the other plugin so they don’t have to be responsible to update the library.

    There is, clearly, an argument to be made in both cases. There isn’t a win here. Personally, I think once a framework or library exists as a plugin in the .org repository, you should remove it from your plugins and require it. Of course, good luck figuring out how to do that in a sane way without breaking people. The best I came up with was have a period of time where you keep the library while using TGM or something to require the other plugin. Make an alert or notice to tell users to install the requirement. Keep that for a whole major version. Then, on the next major version release, drop the library.

    With all that in mind, we have to ask this instead “Which option annoys users slightly less?”

    That’s – libraries as libraries, not plugins. The one where the users don’t have to know (or care) about it anything except “I have plugin X.”