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!

Reader Interactions

%d bloggers like this: