Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Plural URLs

    Plural URLs

    URLs can be hard. When you have custom post types in WordPress it can be harder.

    Take, for example, a custom post type for videos. Do you want your URLs to be http://example.com/videos/video-name/ or http://example.com/video/video-name/ ? And do you want the archive to be http://example.com/videos/ or http://example.com/video/ ? And what happens when you change your mind?

    Thankfully, WordPress lets you do some weird things.

    Pick Your Default

    Let’s look at the video/videos idea for a moment. Individual posts should be video but the archive should be videos in order to grammatically make sense. When you make your custom post type there’s a parameter called has_archive – by default it’s false. If you make it true, then it’ll have the same ‘base’ as your custom post types.

    But. If you make it a string then you can make it ‘videos’ or ‘photos’ and magically your archives will have those names. That makes it pretty easy to change, just remember to re-save your permalinks after. I personally recommend doing a redirect so that video goes to videos (and videos/postname go to video/postname) so that any random bad URLs would still be caught.

    Remember that you can leave it false and make a page to be a placeholder page, or you use archive-{post_type}.php to customize it further.

    When You Need Both

    But… What if you need both?

    This is probably a bad idea, but let’s pretend you want to have both video and videos work for all cases. That’s when you’ll need something like this:

    $plural_types = array( 
    	'videos' => 'post_type_videos', 
    	'photos' => 'post_type_photos' 
    );
    
    foreach( $plural_types as $slug => $type ) {
    	add_rewrite_rule(
    		'^'.$slug.'/?$',
    		'index.php?post_type='.$type,
    		'top'
    	);
    	add_rewrite_rule(
    		'^'.$slug.'/page/([0-9]+)?/?$',
    		'index.php?post_type='.$type.'&paged=$matches[1]',
    		'top'
    	);
    }
    

    In that example, I have the slug for my custom post types set to the singular, and then the $plural_types array has the correct plural and the associated custom post type. This is tossed into a for-loop that creates a custom rewrite rule that will redirect.

  • Expect the Unexpected

    Expect the Unexpected

    The other day, while reviewing a plugin, I told someone that their code was okay, but it could be better.

    They had this:

    if ( $_POST['value'] == 1 ) {
        $variable = yes;
    }
    if ( $_POST['value'] == -1 ) {
        $variable = no;
    }
    

    And I said they should do this:

    $variable = ( $_POST['value'] == 1 )? 'yes' : 'no';
    

    They asked why, since the only possible input were 1 and -1.

    Users Are Weird

    It’s hard to explain why users are so weird, but they are. Any time you have post data that a user can input, a user will find a way to intentionally or accidentally put in bad data. I think perhaps the best way to explain it is that users are like toddlers. You can baby proof your house, but they’ll figure out how to get into the flour and suddenly your kitchen looks like an episode of Cutthroat Kitchen and good luck cleaning it up.

    The point is this. Even if your data is only meant to be a 1 or a -1, you have to think beyond what the code should be and assume it will, one day, be what it’s it.

    Broaden Your Mind

    The basic rule of any input screen is that users will do what they do. They just will. They use code in ways you didn’t imagine, and that’s okay. And even if you have a check box, which logically cannot be altered beyond checked or un-checked, someone will do something outside your expectations.

    The easiest way to understand it is to think about hackers. The whole reason we sanitize checkbox data is not that we expect a user to make a phenomenal mistake, but we expect a hacker to show up and try to back-door our work. We cannot trust that every user has good intentions. This is even more common in WordPress, since anyone can download your code, examine in for weaknesses, and then attack.

    Angry People Do Bad Things

    If I had a nickel for every time I heard “But an admin would never…” I’d be rich.

    A good admin would never, intentionally, break their own system, this is true. But an admin who was just fired, and hasn’t had their credentials revoked yet? Oh gosh, can they ever be evil. When a person was fired at a job I once had, they went into the test lab, took all the diskettes, and tossed them in the dumpster. The protocol for handling people being fired was changed that day, but all it took was one angry admin, and we had to go dumpster diving for 3.5″ floppy disks.

    No, it wasn’t fun.

    Trust No Data

    I never trust data. Not even on code only I use. I always assume I can be tricked into doing something dangerous, or that I’ll make a mistake while using a system. Humans make mistakes. You can’t trust them to be right all the time, and you can’t trust them to be good all the time.

    That means it becomes our responsibility, as developers, to do the following:

    1. Make sure the data entered is sanity-checked
    2. If it’s not sane, fallback to a safe default or throw a good error

    But never, ever, trust anyone to be right all the time. Especially you.

  • Chassis – When VVV is Too Much

    Chassis – When VVV is Too Much

    When I need to do WordPress core development, I use VVV. It’s great for multiple versions of WordPress, a copy of WordPress Meta, and it’s all done in one go.

    But when I’m developing my own code, I want something a little lighter and simpler. I’ve been using Local for that for a while now. It involved a few weird tweaks but I was quite fond of it until the 2.0 upgrade. That’s because they broke the tool I needed most: Addon Volumes.

    The current status is that it’s broken and the developer misjudged how many people used it. These things happen, but for me this was the primary reason I used it. So that meant it was time to look at my options again!

    Chassis

    Made by the quirky and original Human Made, Chassis is a cross between VVV and Local.

    Like VVV, it’s Open Source. Like Local, it’s fast. Like VVV, it’s command line. Like Local, you can map to your hard drive. And that last reason was why I wanted to use it.

    Look. There are a lot of reasons to use Chassis. The fact that it’s a server, so you can test out things like Memcached and PHP versions and upgrades is a big one. The fact that it’s fast to install and setup is another. But at the end of the day, I need my dev environment to do the following things.

    1. Be ‘easy’ to rebuild
    2. Have access to WP-CLI
    3. Boot fast
    4. Have a GUI SQL editor
    5. Use my dev code, where I want it used from

    My Development, My Way

    The thing I hate about most dev environments is that they want you to put your code in their locations. MAMP, VVV, Local, and DesktopServer all prefer you to put your dev code in the folder for your dev site.

    I don’t work that way. All my code for all my WP sites live in ~/Development/repositories/NAME or ~/Development/github/NAME or ~/Development/wordpress/plugins/NAME and this is a system that works for me. I have all my dev code in the Development folder, and I’m consistent about it.

    Furthermore, when I use a local host install to test, I may use the same plugin on multiple sites. I try to reuse as much code as possible, after all.

    This means my headache is always trying to some how symlink my development folders to my development site. With MAMP and Desktop Server I used rsync (and I was sad). With Local I used the broken add-on. With Chassis, it’s actually built in!

    Build The House

    Chassis touts that it wants to be invisible. In order to do that, they separate WordPress and your code, recommending you put your code in the /content/ folder. This is great, but as we mentioned, I want to have my code in another spot, so I need to map folders.

    This can be done in the “Synced Folders” of the config.yaml file. I’ve added this:

    # Synced Folders
    #
    # You can sync as many folders as you like. We sync the nginx and php log folders by default.
    synced_folders:
        logs/nginx: /var/log/nginx
        logs/php: /var/log/php
        /Users/ipstenu/Development/repositories/site1-genesis: /vagrant/content/themes/site1-genesis
        /Users/ipstenu/Development/repositories/site2-genesis: /vagrant/content/themes/site2-genesis
        /Users/ipstenu/Development/repositories/site2-underscores: /vagrant/content/themes/site2-underscores
        /Users/ipstenu/Development/repositories/site1-plugin: /vagrant/content/plugins/site1-plugin
        /Users/ipstenu/Development/repositories/site2-plugin: /vagrant/content/plugins/site2-plugin
        #/Users/ipstenu/Development/repositories/site-mu-plugins: /vagrant/content/mu-plugins
        /Users/ipstenu/Development/repositories/site-mu-plugins: /vagrant/wp/wp-content/mu-plugins
    

    Run a reload and a provision of vagrant and it all worked. That’s right, it was all silently symlinked and had full access to all my code in all the right places… Except…

    Mostly Ugly Plugins

    You may notice this:

        #/Users/ipstenu/Development/repositories/site-mu-plugins: /vagrant/content/mu-plugins
        /Users/ipstenu/Development/repositories/site-mu-plugins: /vagrant/wp/wp-content/mu-plugins
    

    The first one didn’t work. The second one did, but only when I added this to my local-config.php file:

    define( 'WPMU_PLUGIN_DIR', '/vagrant/wp/wp-content/mu-plugins' );
    define( 'WPMU_PLUGIN_URL', $_SERVER['HTTP_HOST'] . '/wp-content/mu-plugins' );
    

    I’m still not sure if I broke it or if they did. What I do know is that I can rather easily build out my dev server, point it to my dev code, and everything’s working.

    Conclusion: Should you use Chassis?

    I firmly hold that all developers should be familiar with the shell. Maybe they’re not all golden goddesses, but they should know how to get around, list files, and Google the basic commands like links, rsync, move, copy, and delete. With that in mind, if you’re a developer (be it a code developer, a design developer, or anyone else who peeks under the cover at the code), you should give Chassis a try.

    It’s open source, so you can learn from it if you’re so inclined. It’s command line, so you can script it if you’re so inclined. You can separate your plugins from the plugins of the extensions, like debugging, and you can write your own if you want.

    Basically yes, you should use Chassis. It won’t be everything to everyone, but it can be something for most people.

  • Secure Mindsets in Plugins

    Secure Mindsets in Plugins

    At WordCamp Europe last week, I talked about the basics of plugin development. Since I had a mixed bag of experiences, I decided not to actually write a plugin in the class, but instead I took Hello Dolly and edited it. I discussed how the plugin worked, that an action called a function, which returned a value, and showed the interconnectivity. In this way, the attendees could understand the big picture of how code comes together.

    But at the end, with five minutes, I touched on an important aspect of plugins that Hello Dolly doesn’t do much with, because it doesn’t have to.

    I talked about security.

    Past You

    In the past, you probably done insecure things. Have you ever left your car unlocked in the driveway while you ran the groceries inside? We all do things that are insecure or unsafe. This is normal. Similarly, we have done insecure code. In the past, all of us, when we begin, we write code to perform actions without thinking about how it will be used globally. We don’t worry about safe, we worry about functions.

    There’s nothing wrong with this. We are often focus driven designers, fueled by passion and desire, so we want to do and not worry about the details.

    This Morning’s You

    That said, when we do work in that way, we get ourselves into trouble when we ignore security. We assume people will only use the code in the right way, because it’s obvious what is right and what is wrong. I try not to say ‘obvious’ or ‘simple’ when talking about code or interfaces, because they are absolutely never, not once, obvious or simple. When I got my Apple Watch, the UX of Force Touch wasn’t obvious to me. It’s not simple now, since it can be a bit touchy, but it’s not difficult.

    In the same vein, we all know that users do weird shit. Really weird shit. They put text in fields that should only gen numbers. They put numbers in for email. They copy paste without thinking. And you know that. You’ve seen it.

    The Right Now You

    Having read that, you’re hopefully thinking “how can I make my code secure?”

    When we talk about basic security, we mean four things:

    1. Validate your data
    2. Sanitize what you save
    3. Escape what you output
    4. Verify a human meant to do it

    That’s it. Make sure a date is a date and an email is an email. Make sure you save the data in a way that doesn’t put other data at risk. Remove any possibly dangerous characters from what you show to users. Always make sure someone meant to do the action. WordPress has over a dozen Sanitize and Escape functions to help make sure you save the right data and it has nonces to help make sure you save when you should.

    They’re very complex, but at their heart, they do those four things.

    Future You

    The you of tomorrow will appreciate the you of today, if you remember to never trust your data. People typo. People make mistakes. People do bad things on purpose. All of that just happens. And if today, you learn how stop those bad things, tomorrow’s you will look back on you with love and thanks. Your users will thank you. Your next future will love you even more.

    Security isn’t just https and good passwords. It’s a mindset to remember that anything passed to your code might be attacked. It’s a mindset that good users do bad and dumb things. It’s a mindset that mistakes happen. And it’s a mindset that being aware of the whole of your code, how it all comes together, must always include validation, sanitization, escaping, and nonces.

  • A Fully Functional Alexa Skill

    A Fully Functional Alexa Skill

    I’ve been talking a lot (a lot) about my Amazon Alexa skill.

    First of all, it’s done. It’s published and working. You can check it out on Amazon.com.

    Secondly, a lot of people are like me and need a real, concrete, working example. So I’ve decided to post the entirety of my Amazon Alexa Skill.

    This file, which is actually found on a site in /lwtv-plugin/rest-api/alexa-skills.php consists of two endpoints: the flash briefing and the skill. The skill is called “Bury Your Queers” and while I suspect that part of the code is the least ‘useful’ to people, it’s also good to see the real code.

    Here, then, is the original approved and certified WordPress code for an Alexa Skill:

    The Code

    <?php
    /*
    Description: REST-API - Alexa Skills
    
    For Amazon Alexa Skills
    
    Version: 1.0
    Author: Mika Epstein
    */
    
    if ( ! defined('WPINC' ) ) die;
    
    /**
     * class LWTV_Alexa_Skills
     *
     * The basic constructor class that will set up our JSON API.
     */
    class LWTV_Alexa_Skills {
    
    	/**
    	 * Constructor
    	 */
    	public function __construct() {
    		add_action( 'rest_api_init', array( $this, 'rest_api_init') );
    	}
    
    	/**
    	 * Rest API init
    	 *
    	 * Creates callbacks
    	 *   - /lwtv/v1/flash-briefing
    	 */
    	public function rest_api_init() {
    
    		// Skills
    		register_rest_route( 'lwtv/v1', '/alexa-skills/briefing/', array(
    			'methods' => 'GET',
    			'callback' => array( $this, 'flash_briefing_rest_api_callback' ),
    		) );
    
    		// Skills
    		register_rest_route( 'lwtv/v1', '/alexa-skills/byq/', array(
    			'methods' => [ 'GET', 'POST' ],
    			'callback' => array( $this, 'bury_your_queers_rest_api_callback' ),
    		) );
    
    
    	}
    
    	/**
    	 * Rest API Callback for Flash Briefing
    	 */
    	public function flash_briefing_rest_api_callback( $data ) {
    		$response = $this->flash_briefing();
    		return $response;
    	}
    
    	/**
    	 * Rest API Callback for Bury Your Queers
    	 * This accepts POST data
    	 */
    	public function bury_your_queers_rest_api_callback( WP_REST_Request $request ) {
    
    		$type   = ( isset( $request['request']['type'] ) )? $request['request']['type'] : false;
    		$intent = ( isset( $request['request']['intent']['name'] ) )? $request['request']['intent']['name'] : false;
    		$date   = ( isset( $request['request']['intent']['slots']['Date']['value'] ) )? $request['request']['intent']['slots']['Date']['value'] : false;
    
    		$validate_alexa = $this->alexa_validate_request( $request );
    
    		if ( $validate_alexa['success'] != 1 ) {
    			$error = new WP_REST_Response( array( 'message' => $validate_alexa['message'], 'data' => array( 'status' => 400 ) ) );
    			$error->set_status( 400 );
    			return $error;
    		}
    
    		$response = $this->bury_your_queers( $type, $intent, $date );
    		return $response;
    	}
    
    
    	function alexa_validate_request( $request ) {
    
    		$chain_url = $request->get_header( 'signaturecertchainurl' );
    		$timestamp = $request['request']['timestamp'];
    		$signature = $request->get_header( 'signature' );
    
    	    // Validate that it even came from Amazon ...
    	    if ( !isset( $chain_url ) )
    	    	return array( 'success' => 0, 'message' => 'This request did not come from Amazon.' );
    
    	    // Validate proper format of Amazon provided certificate chain url
    	    $valid_uri = $this->alexa_valid_key_chain_uri( $chain_url );
    	    if ( $valid_uri != 1 )
    	    	return array( 'success' => 0, 'message' => $valid_uri );
    
    	    // Validate certificate signature
    	    $valid_cert = $this->alexa_valid_cert( $request, $chain_url, $signature );
    	    if ( $valid_cert != 1 )
    	    	return array ( 'success' => 0, 'message' => $valid_cert );
    
    	    // Validate time stamp
    		if (time() - strtotime( $timestamp ) > 60)
    			return array ( 'success' => 0, 'message' => 'Timestamp validation failure. Current time: ' . time() . ' vs. Timestamp: ' . $timestamp );
    
    	    return array( 'success' => 1, 'message' => 'Success' );
    	}
    
    	/*
    		Validate certificate chain URL
    	*/
    	function alexa_valid_key_chain_uri( $keychainUri ){
    
    	    $uriParts = parse_url( $keychainUri );
    
    	    if (strcasecmp( $uriParts['host'], 's3.amazonaws.com' ) != 0 )
    	        return ( 'The host for the Certificate provided in the header is invalid' );
    
    	    if (strpos( $uriParts['path'], '/echo.api/' ) !== 0 )
    	        return ( 'The URL path for the Certificate provided in the header is invalid' );
    
    	    if (strcasecmp( $uriParts['scheme'], 'https' ) != 0 )
    	        return ( 'The URL is using an unsupported scheme. Should be https' );
    
    	    if (array_key_exists( 'port', $uriParts ) && $uriParts['port'] != '443' )
    	        return ( 'The URL is using an unsupported https port' );
    
    	    return 1;
    	}
    
    	/*
    	    Validate that the certificate and signature are valid
    	*/
    	function alexa_valid_cert( $request, $chain_url, $signature ) {
    
    		$md5pem     = get_temp_dir() . md5( $chain_url ) . '.pem';
    	    $echoDomain = 'echo-api.amazon.com';
    
    	    // If we haven't received a certificate with this URL before,
    	    // store it as a cached copy
    	    if ( !file_exists( $md5pem ) ) {
    		    file_put_contents( $md5pem, file_get_contents( $chain_url ) );
    		}
    
    	    $pem = file_get_contents( $md5pem );
    
    	    // Validate certificate chain and signature
    	    $ssl_check = openssl_verify( $request->get_body() , base64_decode( $signature ), $pem, 'sha1' );
    
    	    if ($ssl_check != 1 ) {
    		    return( openssl_error_string() );
    		}
    
    	    // Parse certificate for validations below
    	    $parsedCertificate = openssl_x509_parse( $pem );
    	    if ( !$parsedCertificate ) return( 'x509 parsing failed' );
    
    	    // Check that the domain echo-api.amazon.com is present in
    	    // the Subject Alternative Names (SANs) section of the signing certificate
    	    if(strpos( $parsedCertificate['extensions']['subjectAltName'], $echoDomain) === false) {
    	        return( 'subjectAltName Check Failed' );
    	    }
    
    	    // Check that the signing certificate has not expired
    	    // (examine both the Not Before and Not After dates)
    	    $validFrom = $parsedCertificate['validFrom_time_t'];
    	    $validTo   = $parsedCertificate['validTo_time_t'];
    	    $time      = time();
    
    	    if ( !( $validFrom <= $time && $time <= $validTo ) ) {
    	        return( 'certificate expiration check failed' );
    	    }
    
    	    return 1;
    	}
    
    	/**
    	 * Generate the Flash Briefing output
    	 *
    	 * @access public
    	 * @return void
    	 */
    	public function flash_briefing() {
    
    		$query = new WP_Query( array( 'numberposts' => '10' ) );
    		if ( $query->have_posts() ) {
    			while ( $query->have_posts() ) {
    				$query->the_post();
    
    				$response = array(
    					'uid'            => get_the_permalink(),
    					'updateDate'     => get_post_modified_time( 'Y-m-d\TH:i:s.\0\Z' ),
    					'titleText'      => get_the_title(),
    					'mainText'       => get_the_title() . '. ' . get_the_excerpt(),
    					'redirectionUrl' => home_url(),
    				);
    
    				$responses[] = $response;
    			}
    			wp_reset_postdata();
    		}
    
    		if ( count( $responses ) === 1 ) {
    			$responses = $responses[0];
    		}
    
    		return $responses;
    
    	}
    
    	/**
    	 * Generate Bury Your Queers
    	 *
    	 * @access public
    	 * @return void
    	 */
    	public function bury_your_queers( $type = false, $intent = false, $date = false ) {
    
    		$whodied    = '';
    		$endsession = true;
    		$timestamp  = ( strtotime( $date ) == false )? false : strtotime( $date ) ;
    		$helptext   = 'You can find out who died on specific dates by asking me questions like "who died" or "who died today" or "who died on March 3rd" or even "How many died in 2017." If no one died then, I\'ll let you know.';
    
    		if ( $type == 'LaunchRequest' ) {
    			$whodied = 'Welcome to the LezWatch TV Bury Your Queers skill. ' . $helptext;
    			$endsession = false;
    		} else {
    			if ( $intent == 'AMAZON.HelpIntent' ) {
    				$whodied = 'This is the Bury Your Queers skill by LezWatch TV, home of the world\'s greatest database of queer female on TV. ' . $helptext;
    				$endsession = false;
    			} elseif ( $intent == 'AMAZON.StopIntent' || $intent == 'AMAZON.CancelIntent' ) {
    				// Do nothing
    			} elseif ( $intent == 'HowMany' ) {
    				if ( $date == false || $timestamp == false ) {
    					$data     = LWTV_Stats_JSON::statistics( 'death', 'simple' );
    					$whodied  = 'A total of '. $data['characters']['dead'] .' queer female characters have died on TV.';
    				} elseif ( !preg_match( '/^[0-9]{4}$/' , $date ) ) {
    					$whodied    = 'I\'m sorry. I don\'t know how to calculate deaths in anything but years right now. ' . $helptext;
    					$endsession = false;
    				} else {
    					$data     = LWTV_Stats_JSON::statistics( 'death', 'years' );
    					$count    = $data[$date]['count'];
    					$how_many = 'No queer female characters died on TV in ' . $date . '.';
    					if ( $count > 0 ) {
    						$how_many = $count .' queer female ' . _n( 'character', 'characters', $count ) . ' died on TV in ' . $date . '.';
    					}
    					$whodied  = $how_many;
    				}
    			} elseif ( $intent == 'WhoDied' ) {
    				if ( $date == false || $timestamp == false ) {
    					$data    = LWTV_BYQ_JSON::last_death();
    					$name    = $data['name'];
    					$whodied = 'The last queer female to die was '. $name .' on '. date( 'F j, Y', $data['died'] ) .'.';
    				} elseif ( preg_match( '/^[0-9]{4}-(0[1-9]|1[0-2])$/' , $date ) ) {
    					$whodied    = 'I\'m sorry. I don\'t know how to calculate deaths in anything but days right now. ' . $helptext;
    					$endsession = false;
    				} else {
    					$this_day = date('m-d', $timestamp );
    					$data     = LWTV_BYQ_JSON::on_this_day( $this_day );
    					$count    = ( key( $data ) == 'none' )? 0 : count( $data ) ;
    					$how_many = 'No queer females died';
    					$the_dead = '';
    					if ( $count > 0 ) {
    						$how_many  = $count . ' queer female ' . _n( 'character', 'characters', $count ) . ' died';
    						$deadcount = 1;
    						foreach ( $data as $dead_character ) {
    							if ( $deadcount == $count && $count !== 1 ) $the_dead .= 'And ';
    							$the_dead .= $dead_character['name'] . ' in ' . $dead_character['died'] . '. ';
    							$deadcount++;
    						}
    					}
    					$whodied = $how_many . ' on '. date('F jS', $timestamp ) . '. ' . $the_dead;
    				}
    			} else {
    				// We have a weird request...
    				$whodied = 'I\'m sorry, I don\'t understand that request. Please ask me something else.';
    				$endsession = false;
    			}
    		}
    		$response = array(
    			'version'  => '1.0',
    			'response' => array (
    				'outputSpeech' => array (
    					'type' => 'PlainText',
    					'text' => $whodied,
    				),
    				'shouldEndSession' => $endsession,
    			)
    		);
    
    		return $response;
    
    	}
    
    }
    new LWTV_Alexa_Skills();
    

    Some Explanations

    The functions bury_your_queers and bury_your_queers_rest_api_callback are the important ones. The flash briefing is there because I was tired of Amazon being picky about embedded media in RSS feeds.

    The way bury_your_queers_rest_api_callback works is it takes the request data to generate the type of request, the intent, and the date information. Then it passes the full request data to alexa_validate_request which is the part you’ll really want.

    That function, alexa_validate_request, is what’s validating that the request came from Amazon, that it’s got a legit certificate from Amazon, and that the request was made in the last 60 seconds. While all those checks kick back an error, the development tools from Amazon will not show you them. Yet. I’m hoping they will in the future so we can more easily debug, but it was a lot of blind debugging. Not my favorite.

    Some Custom Code

    In the bury_your_queers function, I make some calls to other code not included:

    • LWTV_BYQ_JSON::last_death()
    • LWTV_BYQ_JSON::on_this_day( $this_day )

    Those both reference another rest API class in a different file. What’s important here is not what the data is, but that I’m calling those functions and getting an array back, and using that to fill in my reply. For example, here we have the call for ‘last death’:

    $data    = LWTV_BYQ_JSON::last_death();
    $name    = $data['name'];
    $whodied = 'The last queer female to die was '. $name .' on '. date( 'F j, Y', $data['died'] ) .'.';
    

    From this you can infer that the array kicked back has key for name and died. And in fact, if you look at the JSON output, you’ll see if has that and a bit more. I’m just extracting what is required. The same is true of the other function, LWTV_BYQ_JSON::on_this_day, to which I’m passing a parameter of a date.

  • POST Isn’t Just For Posting

    POST Isn’t Just For Posting

    I’m taking a moment here to explain something that confused the hell out of me when I was getting into the JSON API. In short, I was confused about what POST meant in the JSON API.

    POST vs GET

    The official documentation mentions this:

    Want to get your site’s posts? Simply send a GET request to /wp-json/wp/v2/posts. Update user with ID 4? Send a POST request to /wp-json/wp/v2/users/4. Get all posts with the search term “awesome”? GET /wp-json/wp/v2/posts?search=awesome. It’s that easy.

    From that I inferred that if I was updated content on my site, I would use a POST call. Otherwise, I’m always going to be using GET to get data. Makes sense. If you want to get the data on a specific post, you do a get for /wp-json/wp/v2/posts/1234 and get that post’s data. Store the JSON output as a parameter in your plugin, let’s say, and Bob’s your father’s brother’s husband.

    I was wrong.

    Alexa Posts

    When I started working with Alexa I was confounded. It told me it sent a POST request to my site. I stared at that for a while. I’d been assuming that when I asked Alexa something, I’d be able to tell it to do a GET request from /wp-json/MYAPP/v1/MYSKILL/parameter. After all, I’m not posting data.

    But then I thought about it a little bit more. A straightforward GET request gets data from a URL without interaction. A POST posts data to a site, and you decide what to do with it. Most of the time when we think of a POST action happening, we think of updating data.

    POST doesn’t have to mean update

    A POST is just sending data to your JSON API. It posts to your site.

    That’s why passing the WP_REST_Request $request data to your function gives you magical access to the request data. And from that we can grab all the data Alexa requests send to your site, which lets us parse the data and make decisions on our replies.

    Now like I said before, what you do with the POST is up to you. But that explains a lot about why Amazon is so picky about making sure a request came legit from them. Especially since you can order stuff from Amazon…

    “Hey Alexa, can you tell TV shows to stop killing off queer characters?”