Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • (Slightly) More Performant WP Queries

    (Slightly) More Performant WP Queries

    One of the things 10up lists as a best engineering practice is this:

    Do not use posts_per_page => -1.
    This is a performance hazard. What if we have 100,000 posts? This could crash the site. If you are writing a widget, for example, and just want to grab all of a custom post type, determine a reasonable upper limit for your situation.

    This is a very valid point, but I found myself stymied at how to work around it in a case where I knew I needed to check all posts in a custom type. And worse that post type was growing every week by 10 to 20. In my case, the reasonable upper limit was an unknown that was also unpredictable. But an endless loop would also be bad.

    One of the other recommendations from 10up is not to run more queries than needed. I was already using no_found_rows => true to prevent counting the total rows, as it’s really only necessary for pagination. I also force in the post type I’m scanning, which again limits how many possible items will be queried. And yes, I have update_post_meta_cache and update_post_term_cache set to false as in most cases those aren’t needed either.

    But what could I do to make the actual query of getting how many posts of type A had a post meta value that matched post type B? And to make it worse, I could have multiple values in the post metas. It’s really a case where, in retrospect, making them into a custom taxonomy might have been a bit wiser.

    What I decided to do was limit the number of posts queried based on how many posts were in the post type.

    posts_per_page => wp_count_posts( 'custom_post_type' )->publish;

    I’m not quite concerned with 100,000 posts, and I have some database caching installed to mitigate the load. But also I have set an upper limit and this feels less insane than the -1 value. Since I had to generate the count anyway for displaying statistics, I moved that check to a variable and called it twice.

  • Datepicker and a Widget

    Datepicker and a Widget

    Last week, I worked on making a plugin that would safely and smartly allow for a date selection, and not permit people to put in junk data. I had my code ‘clean’ and only accepted good data, there was one question remaining. How do I make it look GOOD?

    Let’s be honest, pretty data matters. If things look good, people use them. It’s that simple. This let me play with another aspect of coding that I don’t generally look at. Javascript.

    What Code Do I Need?

    There are a lot of ways to tackle this kind of problem. If you wanted to just list the months and days, and have those be drop-downs, you could do that. The option I went with was to have a calendar date-picker pop up, as I felt that would visually explain what the data should be.

    To do that I needed a date picker jQuery script (which is included in WordPress core) and a second script to format the output.

    My Script

    This part is really small:

    jQuery(function() {
        jQuery( ".datepicker" ).datepicker({
            dateFormat : "mm-dd"
        });
    });
    

    All it does is force the format to be “mm-dd” – so if you picked the date, that’s what it would be.

    Enqueuing the Scripts

    In order to make sure the scripts are only loaded on the widgets page, my enqueue function looks like this:

    	public function admin_enqueue_scripts($hook) {
    		if( $hook !== 'widgets.php' ) return;
    		wp_enqueue_script( 'byq-onthisday', plugins_url( 'js/otd-datepicker.js', __FILE__ ), array( 'jquery-ui-datepicker' ), $this->version, true );
    		wp_enqueue_style( 'jquery-ui', plugins_url( 'css/jquery-ui.css', __FILE__ ), array(), $this->version );
    	}
    

    The CSS is because, by default, WordPress doesn’t include the jquery UI CSS.

    Calling the Scripts

    In the widget class, I have a function for the form output. In there, I have an input field with a class defined as datepicker, which is used by the jquery I wrote above, to know “I’m the one for you!”

    	function form( $instance ) {
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    		?>
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title', 'bury-your-queers' ); ?>: </label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat" />
    		</p>
    
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'date' ) ); ?>"><?php _e( 'Date (Optional)', 'bury-your-queers' ); ?>: </label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'date' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'date' ) ); ?>" class="datepicker" value="<?php echo esc_attr( $instance['date'] ); ?>" class="widefat" />
    			<br><em><?php _e( 'If blank, the date will be the current day.', 'bury-your-queers' ); ?></em>
    		</p>
    		<?php
    	}
    

    Making it Pretty

    To be honest, once I got the JS working, I left the default CSS alone. Why? Because I’m a monkey with a crayon when it comes to design. The default worked fine for me:

    The Default Date Picker

    It does make me think that it would be nice if WordPress included their own customize datepicker colors in the admin colors, but I understand why they don’t. Not everyone or even most people will ever need this.

  • Always Validate Data

    Always Validate Data

    In the making of my REST API plugin’s extension, I had written out a way to select what happened on a specific date. While adding that kind of code into a shortcode was something I’d done before, I struggled to try and understand how I could do that with a widget in a way that was safe.

    On Data Validation

    The main function that does all the real work expects a date parameter in the format of MM-DD which it converts to YYYY-MM-DD so I can run date() calls on it later on. The reason that works is that the PHP date() function can parse the YYYY-MM-DD format on it’s own. To that end, I have to write the widget and shortcode in such a way that it’s impossible to pass on bad data.

    This is a matter of an age old problem: sanitize, escape, and validate.

    If you’ve written a plugin and think that sounds familiar, yes, it is a common sticking point of mine. You must sanitize your data before you save it. You must escape it before you display it. You should validate it to make sure you’re not letting people make silly mistakes.

    The ‘problem’ is that there are a lot of ways to do that, and the ultimate question is this “What is the data I’m trying to save supposed to look like?” If it’s a URL, you sanitize for a URL. If it’s a number, you sanitize for a number. That’s the easy part. It’s just really time consuming and difficult. Still. Sanitize early, escape late, and always validate.

    The Main Function Sanitizes and Validates

    Instead of only having the shortcode or the widget make sure the date was right, I decided to attack the matter further up the chain. Or down the chain, depending on your point of view. Both the shortcode and widget call the same master function so for that, I make sure that the value of $this_day is never anything than what I want:

    	public static function on_this_day( $this_day = 'today' ) {
    		$this_day = sanitize_text_field( $this_day );
    		if ( $this_day !== 'today' ) {
    			$month = substr( $this_day, 0, 2);
    			$day = substr( $this_day, 3, 2);
    			$this_day = ( checkdate ( $month, $day , date('Y') ) == true )? $this_day : 'today' ;	
    		}
    
    	[... the rest of the code continues on]
    	}
    

    By using checkdate(), I verify that the value is either a valid date or I force it to be ‘today’. This prevents someone from passing bad data in the shortcode. I sanitize it as well as validate, using sanitize_text_field() because there’s no reason to even allow non-safe data anywhere further in my code.

    The Shortcode Validates and Sanitizes

    The way a shortcode works in WordPress is you can pass attributes to it and the shortcode parses that for you. My shortcode is pretty basic: [on-this-day date="MM-DD"] But since a shortcode lives in the world of ‘users can enter whatever they want’, I have to play Captain Beaver Dam and stop them from using [on-this-day date="Elvis"] for example.

    I do this by sanitizing the attribute and then validating it’s the format I think it is:

    	public function on_this_day_shortcode( $atts = [] ) {
    		$attributes = shortcode_atts([
    			'date' => 'today',
    		], $atts);
    
    		$this_day = sanitize_text_field($attributes['date']);
    		if ( $this_day !== 'today' ) {
    			$month = substr( $this_day, 0, 2);
    			$day = substr( $this_day, 3, 2);
    			$this_day = ( checkdate ( $month, $day , date('Y') ) == true )? $this_day : 'today' ;
    		}
    		$onthisday = $this->on_this_day( $this_day );
    		return $onthisday;
    	}
    

    If you’re thinking that looks the same as what I do in the main widget, you’re right! I do it here to make sure I’m never sending bad data to the function. I do it in the function since I’m not actually psychic and people have done some pretty weird things. Also they may decide to extend my code one day and write something that calls the function.

    The Widget Validates and Sanitizes

    At this point, you’re expecting this, right? It’s actually more important that the widget do this than anything else. The code on my server won’t accept incorrectly formatted data, so the worst that happens is you get a bad data reply. The reason all this matters more for the widget though is that the widget stores data in the database.

    If you’re saving data and you’re not making it safe, you’re putting yourself at risk. Repeat that until it’s second nature.

    In my widget, there’s an update function that saves the data to the database.

    	function update( $new_instance, $old_instance ) {
    		$new_instance['title'] = strip_tags( $new_instance['title'] );
    
    		$new_instance['date'] = substr( $new_instance['date'], 0, 5);
    		$month = substr( $new_instance['date'], 0, 2);
    		$day = substr( $new_instance['date'], 3, 2);
    		if ( checkdate( $month, $day, date("Y") ) == false ) $new_instance['date'] = '';
    		$new_instance['date']  = strip_tags( $new_instance['date'] );
    
    		return $new_instance;
    	}
    

    You’ll notice that, yet again, I use checkdate() to make sure the date is a valid value. But I’m also using strip_tags() to remove any HTML. You could use wp_strip_all_tags() which is even more hardcore, but in this moment, they both work.

    The Data Is Cleaned Three Ways

    In a way, it’s overkill. I don’t need to validate the data in the shortcode really, because I’m doing that in the main function. I do need to validate in the widget, since I want to make sure I’m not saving bad data, and I don’t want people to see invalid data on the widget settings page. At the same time, by making sure everything is as sane and secure and valid, I’m limiting possible vulnerabilities down the road.

  • REST API On This Day

    REST API On This Day

    After making a simple REST API output that showed the date (and time since) the last death listed on a site, I had an idea. What if I could list everyone who died on a specific day? I had that data after all, so why not.

    A Word About Evolution of Code

    Over time, the code behind my “Bury Your Characters” plugin evolved a great deal. I went from unique function names to singletons to one aspect and now to two. The design changed a lot in the middle as well. I unpacked the parsing of data from being on the data driven site and moved that to the site running the plugin.

    The evolution of all this was for a simple reason. I wanted the JSON output to be generated as fast as possible, grabbed, and parsed locally. If the data takes less time to generate on my end, then the speed becomes more of a factor of how fast can the end-user’s site run. That would allow more people to use my plugin.

    A secondary bonus reason was that I hate repeating myself in code. If I only have one place to update my code that generates the list of dead characters, for example, then there are fewer odds I’ll screw up and only edit one of the six places it gets used.

    This means that the code I posted last week doesn’t look at all like that anymore.

    On My Site (aka The Service)

    In a file called bury-your-queers.php (yes, this is for that site) I have a class called LWTV_BYQ_JSON.

    In that class I have a constructor that calls the rest_api_init and that’s where I define my URLs. I already had the last-death set, so now I’ve added in two more for on-this-day:

    	public function rest_api_init() {
    		register_rest_route( 'lwtv/v1', '/last-death', array(
    			'methods' => 'GET',
    			'callback' => array( $this, 'last_death_rest_api_callback' ),
    		) );
    		register_rest_route( 'lwtv/v1', '/on-this-day/', array(
    			'methods' => 'GET',
    			'callback' => array( $this, 'on_this_day_rest_api_callback' ),
    		) );
    		register_rest_route( 'lwtv/v1', '/on-this-day/(?P<date>[\d]{2}-[\d]{2})', array(
    			'methods' => 'GET',
    			'callback' => array( $this, 'on_this_day_rest_api_callback' ),
    		) );
    	}
    

    The reason I have two for my new route is that I wanted both the main URL and the sub URL to work. That is /lwtv/v1/on-this-day/ and /lwtv/v1/on-this-day/03-03/ both work. They both call the same callback which checks what was passed to it. If the params for date are empty, it assumes today:

    	public function on_this_day_rest_api_callback( $data ) {
    		$params = $data->get_params();
    		$this_day = ( $params['date'] !== '' )? $params['date'] : 'today';
    		$response = $this->on_this_day( $this_day );
    		return $response;
    	}
    

    The callback grabs the function on_this_day(), passing the date through to it. Now this is the first BIG change. I pulled the code to generate the list of all the dead out of the function for last_death because it’s called twice. Now it calls LWTV_Loops::post_meta_query which is just a class for all my common loops that get reused a lot. They’re no faster than making the query the regular way, they’re just neater and tidier.

    	public static function on_this_day( $this_day = 'today' ) {
    
    		if ( $this_day == 'today' ) {
    			$this_day = date('m-d');
    		}
    
    		// Get all our dead queers
    		$dead_chars_loop  = LWTV_Loops::post_meta_query( 'post_type_characters', 'lezchars_death_year', '', 'EXISTS' );
    		$dead_chars_query = wp_list_pluck( $dead_chars_loop->posts, 'ID' );
    		$death_list_array = self::list_of_dead_characters( $dead_chars_query, $dead_chars_loop );
    
    		$died_today_array = array();
    
    		foreach ( $death_list_array as $the_dead ) {
    			if ( $this_day == date('m-d', $the_dead['died'] ) ) {
    				$died_today_array[ $the_dead['slug'] ] = array(
    					'slug' => $the_dead['slug'],
    					'name' => $the_dead['name'],
    					'url'  => $the_dead['url'],
    					'died' => date( 'Y', $the_dead['died'] ),
    				);
    			}
    		}
    
    		if ( empty( $died_today_array ) ) {
    			$died_today_array[ 'none' ] = array(
    				'slug' => 'none',
    				'name' => 'No One',
    				'url'  => site_url( '/cliche/dead/' ),
    				'died' => date('m-d'),
    			);
    		}
    
    		$return = $died_today_array;
    		return $return;
    	}
    

    Output

    And all that works to output this (if you pick 03-03):

    {"alisa-davies":{"slug":"alisa-davies","name":"Alisa Davies","url":"https:\/\/lezwatchtv.com\/character\/alisa-davies\/","died":"2010"},"lexa":{"slug":"lexa","name":"Lexa","url":"https:\/\/lezwatchtv.com\/character\/lexa\/","died":"2016"}}
    

    or this (if you pick a date where no one died):

    {"none":{"slug":"none","name":"No One","url":"https:\/\/lezwatchtv.com\/cliche\/dead\/","died":"02-19"}}
    

    Unlike what I did for ‘last death’, here I’m only reporting the year. The reason is that’s all I care about just now. I’m passing the date through, so I don’t need that information anymore.

    On Your Site (aka The Plugin)

    The plugin for this has three classes. The first is the master class to build out the widgets and shortcodes and also to parse the data. The other two are for the widgets.

    Parsing the data is what we’re going to talk about today.

    The function is straightforward. You pass the date to the function, it grabs the data for the date (defaulting to ‘today’ if none is provided) and spit out the resulting data:

    	public static function on_this_day( $this_day = 'today' ) {
    		$echo_day = ( $this_day == 'today' )? time() : strtotime( date('Y').'-'.$this_day );
    		$json_day = ( $this_day == 'today' )? '' : $this_day.'/' ;
    
    		$request  = wp_remote_get( 'https://lezwatchtv.com/wp-json/lwtv/v1/on-this-day/'.$json_day );
    		$response = wp_remote_retrieve_body( $request );
    		$response = json_decode($response, true);
    
    		$count = ( key($response) == 'none' )? 0 : count($response) ;
    		$how_many = __('no characters died!', 'bury-your-queers');
    		$the_dead = '';
    
    		if ( $count > 0 ) {
    			$how_many = sprintf( _n( '%s character died:', '%s queer female characters died:', $count, 'bury-your-queers' ), $count );
    
    			$the_dead = '<ul class="byq-otd">';
    			foreach ( $response as $dead_character ) {
    				$the_dead .= '<li><a href="'.$dead_character['url'].'">'.$dead_character['name'].'</a> - '.$dead_character['died'] .'</li>';
    			}
    			$the_dead .= '</ul>';
    		}
    
    		$onthisday = '<p>'. sprintf( __( 'On %s, %s', 'bury-your-queers'), date('F jS', $echo_day ), $how_many ).'</p>';
    		$return = $onthisday.$the_dead;
    
    		return $return;
    	}
    

    I picked paragraphs and un-ordered lists, since I feel those would be easily formatted by most people.

  • JSON Rest API (In Peace)

    JSON Rest API (In Peace)

    This week I’ve been talking about my list of dead characters, which I needed to order by year and wanted to display the most recent death in a widget.

    Part of my end goal with all this was a dream I had to let people have a widget on their own site where they could display the most recently dead character. There were other ideas I had, like a ‘this day in YEAR, Character X died.’ But for now, I wanted to start my delving into the JSON API with something more simple.

    Because this is my first serious go at it.

    Sketching Out The Concept

    To do this, I broke my concept down into the logical steps of what was needed.

    • Output the data
    • Create a JSON URL
    • Format the data there

    And no, I have no idea how to do any of that, except outputting the data.

    The Rest API

    To initialize the Rest API, you have to call a function on init() and that’s where it defines the URL that will be called. There are three (or four) parts to a URL:

    • Namespace
    • Version
    • Route
    • Arguments

    The version isn’t technically required, but in the interests of future proofing, it’s probably a good idea. And in this specific case, I don’t have any arguments I want to pass through. You’re going to get just the output of the last dead. In deciding that, I was able to determine the most sensible structure.

    My namespace should be for my site, not just this ‘show the dead’ feature. This is not always going to be the case, but since I’m adding in what I presume will be the first of some APIs, it’s wise to name in a way that is forward thinking. Similarly, I need my route to be logically named, and in this case I’m showing the last death, so I called it last-death.

    This makes my desired URL /wp-json/MYSITE/v1/last-death/ and the code is this:

    public function rest_api_init() {
    	register_rest_route( 'MYSITE/v1', '/last-death', array(
    		'methods' => 'GET',
    		'callback' => array( $this, 'rest_api_callback' ),
    	) );
    }
    

    The callback code is what gets my data, and based on my original widget resulted in the page displaying the following:

    "It has been <strong>2 months and 6 days<\/strong> since the last death: <a href=\"https:\/\/example.dev\/character\/gina\/\">Gina<\/a> - December 7, 2016"
    

    But I didn’t want it to be formatted like that. Instead, I wanted the code to spit out an array. To do that with my existing setup, I rewrote the dead_char() function, taking out all the parts that generated the days since the last death and instead put that in a separate plugin. Now this one gives an API output:

    {"name":"Gina","url":"https:\/\/example.dev\/character\/gina\/","died":1481068800,"since":"5792233"}
    

    That has the added bonus of letting anyone who wants to call it on their own for whatever they want isn’t stuck with my design. Yay, open source!

    The Code

    My JSON code went into a class like this:

    class MYSITE_Dead_Character_JSON {
    
    	/**
    	 * Constructor
    	 */
    	public function __construct() {
    		add_action( 'init', array( $this, 'init') );
    	}
    
    	/**
    	 * Init
    	 */
    	public function init() {
    		add_action( 'rest_api_init', array( $this, 'rest_api_init') );
    	}
    
    	/**
    	 * Rest API init
    	 *
    	 * Creates the callback - /MYSITE/v1/last-death/
    	 */
    	public function rest_api_init() {
    		register_rest_route( 'lwtv/v1', '/last-death', array(
    			'methods' => 'GET',
    			'callback' => array( $this, 'last_death_rest_api_callback' ),
    		) );
    	}
    
    	/**
    	 * Rest API Callback
    	 */
    	public function last_death_rest_api_callback( $data ) {
    		$response = $this->last_death();
    		return $response;
    	}
    
    	/**
    	 * Generate List of Dead
    	 *
    	 * @return array with last dead character data
    	 */
    	public static function last_death() {
    		// Get all our dead queers
    		$dead_chars_loop  = MYSITE_tax_query( 'post_type_characters' , 'cliches', 'slug', 'dead');
    		$dead_chars_query = wp_list_pluck( $dead_chars_loop->posts, 'ID' );
    
    		// List all queers and the year they died
    		if ( $dead_chars_loop->have_posts() ) {
    			$death_list_array = array();
    
    			// Loop through characters to build our list
    			foreach( $dead_chars_query as $dead_char ) {
    
    				// Date(s) character died
    				$died_date = get_post_meta( $dead_char, 'lezchars_death_year', true);
    				$died_date_array = array();
    
    				// For each death date, create an item in an array with the unix timestamp
    				foreach ( $died_date as $date ) {
    					$date_parse = date_parse_from_format( 'm/d/Y' , $date);
    					$died_date_array[] = mktime( $date_parse['hour'], $date_parse['minute'], $date_parse['second'], $date_parse['month'], $date_parse['day'], $date_parse['year'] );
    				}
    
    				// Grab the highest date (aka most recent)
    				$died = max( $died_date_array );
    
    				// Get the post slug
    				$post_slug = get_post_field( 'post_name', get_post( $dead_char ) );
    
    				// Add this character to the array
    				$death_list_array[$post_slug] = array(
    					'name' => get_the_title( $dead_char ),
    					'url' => get_the_permalink( $dead_char ),
    					'died' => $died,
    				);
    			}
    
    			// Reorder all the dead to sort by DoD
    			uasort($death_list_array, function($a, $b) {
    				return $a['died'] <=> $b['died'];
    			});
    		}
    
    		// Extract the last death
    		$last_death = array_slice($death_list_array, -1, 1, true);
    		$last_death = array_shift($last_death);
    
    		// Calculate the difference between then and now
    		$diff = abs( time() - $last_death['died'] );
    		$last_death['since'] = $diff;
    
    		$return = $last_death;
    
    		return $return;
    	}
    
    }
    new MYSITE_Dead_JSON();
    

    You may notice that I don’t seem to have a loop, and I call this instead:

    $dead_chars_loop  = MYSITE_tax_query( 'post_type_characters' , 'cliches', 'slug', 'dead');
    

    For various reasons, I reuse a lot of loop calls. To make my own theme and plugin more human readable, that function does the query. It looks like this:

    function MYSITE_tax_query( $post_type, $taxonomy, $field, $term, $operator = 'IN' ) {
    	$query = new WP_Query ( array(
    		'post_type'       => $post_type,
    		'posts_per_page'  => -1,
    		'no_found_rows'   => true,
    		'post_status'     => array( 'publish', 'draft' ),
    		'tax_query' => array( array(
    			'taxonomy' => $taxonomy,
    			'field'    => $field,
    			'terms'    => $term,
    			'operator' => $operator,
    		),),
    	) );
    	wp_reset_query();
    	return $query;
    }
    

    Since I have to do a lot of odd calls for the statistics on that site, it became smarter to do it that way.

    Calling the Data From YOUR Site!

    I made an endpoint! This is all well and good, but the new question is “How does someone else include this on their site?”

    The answer there is they need a widget. And it’s a widget I’ve mostly already made! All I had to do was create a plugin that made the widget and instead of calling the loops locally, just call the API.

    class Bury_Your_Dead {
    
    	public function __construct() {
    		add_action( 'widgets_init', array( $this, 'last_death_register_widget' ) );
    	}
    
    	public function last_death_register_widget() {
    		$this->widget = new BYD_Last_Death_Widget();
    		register_widget( $this->widget );
    	}
    
    	public static function last_death() {
    		$request  = wp_remote_get( 'https://example.dev/wp-json/MYSITE/v1/last-death/' );
    		$response = wp_remote_retrieve_body( $request );
    		$response = json_decode($response, true);
    
    		$diff = $response['since'];
    
    		$years = floor($diff / (365*60*60*24));
    		$months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24));
    		$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
    
    		$since = '';
    		if ( $years != 0 ) $since .= sprintf( _n( '%s year, ', '%s years, ', $years, 'MYSITE' ), $years );
    		if ( $months != 0 ) $since .= sprintf( _n( '%s month', '%s months', $months, 'MYSITE' ), $months );
    		$since .= ( $years != 0 )? ', ' : ' ';
    		$since .= ( $months != 0 )? __('and ', 'MYSITE') : '';
    		if ( $days != 0 ) $since .= sprintf( _n( '%s day', '%s days', $days, 'MYSITE' ), $days );
    
    		$response['since'] = $since;
    
    		return $response;
    	}
    }
    new Bury_Your_Dead();
    
    class BYD_Last_Death_Widget extends WP_Widget {
    
    	protected $defaults;
    
    	function __construct() {
    
    		$this->defaults = array(
    			'title'		=> __( 'The Most Recent Death', 'MYSITE' ),
    		);
    
    		$widget_ops = array(
    			'classname'   => 'dead-character deadwidget',
    			'description' => __( 'Displays time since the last WLW death', 'MYSITE' ),
    		);
    
    		$control_ops = array(
    			'id_base' => 'lezwatch-dead-char',
    		);
    
    		parent::__construct( 'lezwatch-dead-char', __( 'The Latest Dead', 'MYSITE' ), $widget_ops, $control_ops );
    	}
    
    	function widget( $args, $instance ) {
    
    		extract( $args );
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    
    		echo $args['before_widget'];
    
    		if ( ! empty( $instance['title'] ) ) {
    			echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
    		}
    
    		$dead_character = Bury_Your_Dead::last_death();
    
    		echo sprintf( __('It has been %s since the last death', 'MYSITE'), '<strong>'.$dead_character['since'].'</strong>' );
    		echo ': <a href="'.$dead_character['url'].'">'.$dead_character['name'].'</a> - '.date('F j, Y', $dead_character['died'] );
    
    		echo $args['after_widget'];
    	}
    
    	function update( $new_instance, $old_instance ) {
    		$new_instance['title'] = strip_tags( $new_instance['title'] );
    		return $new_instance;
    	}
    
    	function form( $instance ) {
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    
    		?>
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title', 'MYSITE' ); ?>: </label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat" />
    		</p>
    		<?php
    	}
    }
    

    Whew. That’s huge, I know! And you may have noticed I snuck some translation in there. Always look forward!

  • It Has Been X Days Widget

    It Has Been X Days Widget

    Yesterday I talked about ordering posts by post meta in a weirdly unique situation. Today I’m going to tell you about an even weirder one.

    As I mentioned, what I’m working with is a list of all the dead characters recorded on a site. One of the aspects of this is to allow for us to show a widget that tells visitors how many days it’s been since the last death, and who it was. There are a lot of ways to do this, and one of them is manually updating, but no one likes this. If an event can be automated, everyone wins.

    Since I’d already done the lion’s share of the work, forking it over into a widget and grabbing the last (i.e. most recent) death was easier.

    The Code

    I broke this up into two classes because it’s habit. I have no other reason.

    class Dead {
    	public function __construct() {
    		add_action( 'widgets_init', array( $this, 'register_widget' ) );
    	}
    
    	public function register_widget() {
    		$this->widget = new Dead_Widget();
    		register_widget( $this->widget );
    	}
    
    	public static function dead_queers() {
    		// Get all our dead queers
    		$dead_chars_loop = new WP_Query( array(
    			'post_type'       => 'post_type_characters',
    			'posts_per_page'  => -1,
    			'meta_query'      => array(
    				array(
    					'key'     => 'chars_death_year',
    					'value'   => $thisyear,
    					'compare' => 'REGEXP',
    				),
    			),
    		) );
    		$dead_chars_query = wp_list_pluck( $dead_chars_loop->posts, 'ID' );
    
    		// List all queers and the year they died
    		if ( $dead_chars_loop->have_posts() ) {
    			$death_list_array = array();
    
    			// Loop through characters to build our list
    			foreach( $dead_chars_query as $dead_char ) {
    
    				// Date(s) character died
    				$died_date = get_post_meta( $dead_char, 'lezchars_death_year', true);
    				$died_date_array = array();
    
    				// For each death date, create an item in an array with the unix timestamp
    				foreach ( $died_date as $date ) {
    					$date_parse = date_parse_from_format( 'm/d/Y' , $date);
    					$died_date_array[] = mktime( $date_parse['hour'], $date_parse['minute'], $date_parse['second'], $date_parse['month'], $date_parse['day'], $date_parse['year'] );
    				}
    
    				// Grab the highest date (aka most recent)
    				$died = max( $died_date_array );
    
    				// Get the post slug
    				$post_slug = get_post_field( 'post_name', get_post( $dead_char ) );
    
    				// Add this character to the array
    				$death_list_array[$post_slug] = array(
    					'name' => get_the_title( $dead_char ),
    					'url' => get_the_permalink( $dead_char ),
    					'died' => $died,
    				);
    			}
    
    			// Reorder all the dead to sort by DoD
    			uasort($death_list_array, function($a, $b) {
    				return $a['died'] <=> $b['died'];
    			});
    		}
    
    		// Extract the last death
    		$last_death = array_slice($death_list_array, -1, 1, true);
    		$last_death = array_shift($last_death);
    
    		$diff = abs( time() - $last_death['died'] );
    		$years = floor($diff / (365*60*60*24));
    		$months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24));
    		$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
    
    		$days_since = '';
    		if ( $years != 0 ) $days_since  .= $years .' '. _n( 'year, ', 'years, ', $years );
    		if ( $months != 0 ) $days_since .= $months .' '. _n( 'month', 'months', $months );
    		if ( $years != 0 ) $days_since  .= ',';
    		if ( $months != 0 ) $days_since .= ' and ';
    		if ( $days != 0 ) $days_since   .= $days .' '. _n( 'day', 'days', $days );
    
    		$return = 'It has been <strong>'. $days_since .'</strong> since the last death: <a href="'.$last_death['url'].'">'.$last_death['name'].'</a> - '.date('F j, Y', $last_death['died'] );
    
    		echo $return;
    	}
    }
    new Dead();
    
    class Dead_Widget extends WP_Widget {
    
    	protected $defaults;
    
    	function __construct() {
    		$this->defaults = array(
    			'title'		=> 'Bury Your Dead',
    		);
    
    		$widget_ops = array(
    			'classname'   => 'dead-queer deadwidget',
    			'description' => 'Displays days since the last death',
    		);
    
    		$control_ops = array(
    			'id_base' => 'dead-person',
    		);
    
    		parent::__construct( 'dead-person', 'Days Since Last Dead', $widget_ops, $control_ops );
    	}
    
    	function widget( $args, $instance ) {
    		extract( $args );
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    
    		echo $before_widget;
    
    		if ( ! empty( $instance['title'] ) ) {
    			echo $before_title . apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ) . $after_title;
    		}
    
    		echo Dead::dead_chars();
    
    		echo $after_widget;
    	}
    
    	function update( $new_instance, $old_instance ) {
    		$new_instance['title']      = strip_tags( $new_instance['title'] );
    		return $new_instance;
    	}
    
    	function form( $instance ) {
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    		?>
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">Title:</label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat" />
    		</p>
    		<?php
    	}
    }
    

    Which looks like this:

    Bury Your Queers

    An Explanation

    Some things to note.

    While I only allow for customizing the title, I’ve left my arguments as an array in order to let me extend it later. What if, for example, I want to display the featured image as well?

    I’ve also left in some ‘default’ calls to Genesis’ StudioPress theme with regards to the widget layout. That’s because I wrote this for a StudioPress theme.

    The whole reason the death year is an array is that, as I mentioned the other day, people may die multiple times. Insert Buffy singing “Hey I died twice” here. I have to get all the dates, pick the largest number (which is the most recent date, remember Unix timestamps are forever increasing), and save that one. Once I find the ultimate largest number, I know that’s my character to save. Problem is I have to go through them all to figure out who died last.

    I’m inordinately happy about getting to use array_slice() to grab the final item in an array, and ditto array_shift() to move the multidimensional array to a single dimension.

    Using array_shift() changed this:

    Array ( 
        [gina] => Array (
            [name] => Gina 
            [url] => https://example.dev/character/gina/ 
            [died] => 1481068800 
        )
    )
    

    to this:

    Array ( 
        [name] => Gina 
        [url] => https://example.dev/character/gina/ 
        [died] => 1481068800 
    ) 
    

    And getting to finally use _n() in a non-translation sense was fun.

    The reason the dead characters are in a function in the first class is that I plan to extend this code later on, maybe making a shortcode or a JSON API call. Who knows!

    Finally, I mentioned that there were two ways to list the dead characters. One was by a taxonomy of character clichés and the other was by the death date post meta. In this case, I opted to sort by the taxonomy because if someone has a death date but is not marked as dead, then they were dead and came back to life.