Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: development

  • Names, Short Names, and More Names

    Names, Short Names, and More Names

    I was working on a side project with Tracy Levesque of Yikes! and she lamented at my readme. I had totally half-assed it and I knew it, so she cleaned it up and asked me “What are the shortcodes?” I told her and about a second later she suggested two new names. Hers were better.

    Functionality Based Names

    I have a habit to name things what they are. I made a shorcode, for example, for a number of posts in a custom post type, and I called it [numposts] because that made sense. But when I added in a new one for number of posts in a taxonomy, I made a second shortcode named [numtax] which is kind of silly isn’t it?

    My problem is that I think about each shortcode as it’s own, stand-alone entity. It’s a functional thing. It does a thing. A function should be named uniquely to be clear what it’s for.

    Usage Based Names

    Perhaps without meaning to, Tracy jolted my brain into thinking about not the developer but the end user. Now, in my head, I thought “The user would know which shortcode to use and doesn’t have to think about more.” But. She suggested this: [plugin-name data="type"]

    I stared at that for a moment and felt the light slap me in the face. I’d named the shortcode for their function, but not for the plugin they were in, which is akin to all those terribly named functions and classes I’m always ranting about. In short order, the plugin was fixed and I turned back to my posts code.

    One Name to Bind Them

    [numposts data="posts" posttype="post-type" term="term-slug" taxonomy="taxonomy-slug"]
    

    The code defaults to a data set of posts and a post type of post since those are the most common usages. After that it’s a fast check “Is this a posts data set or a taxonomy one?” and runs the same code it used to run, passing the data along.

    What’s Really The Difference?

    “But Mika, now people have to type in more!” I hear you say.

    They do. [numposts] defaults to posts like it always did. [numposts posttype="page"] is four characters longer. But with the four extra characters (and really I could have left that out) comes something simpler: people only have to think of one shortcode.

    If they want to count the number of posts, then they just count the number of posts and call it a day. There’s no headache of realizing they meant to get the number of posts with a specific taxonomy. It all just works with one. Remember your terms, and those didn’t change except for needing [numposts data="taxonomy" term="wordpress"] which actually makes it more obvious what you’re doing.

    I have no idea if Tracy meant for me to get this deep into this, but she also knows I spent an hour contemplating the fact that the word ‘read’ exists in multiple tenses, and it’s only by context that I actually know which one anyone meant.

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

  • On Uninstalling WordPress Plugins (and Data)

    On Uninstalling WordPress Plugins (and Data)

    Someone asked me why WordPress always says it’s going to delete files and data when it only removes the files and not the database options. There are two parts of the answer to this, one being a little historical and the other being a bit unhelpful.

    The Message: Delete Files and Data

    Once upon a time, when you uninstalled a WordPress plugin, it looked something like this:

    The Delete Plugin Screen

    That was a very simple screen. You were asked to delete the plugin files, you clicked that you were, done.

    Now you see this thanks to Shiny Updates:

    Shiny Updates - Delete Hello Dolly

    It’s a different message, telling you it’s deleting data!

    What Is The Data?

    The part you don’t see is that WordPress would also remove all the data as well as those files.

    Any time WordPress showed you that message to delete files and data, it was saying that it found a file called uninstall.php which would, presumably, delete the data set by the plugin. By this I mean the options and settings you chose for your plugin. Some plugins have data and others don’t. For example, Hello Dolly has no data, just files. It doesn’t need an uninstall file. On the other hand, a plugin like Jetpack has a lot of settings it should remove from the database on cleanup.

    Why Do We See ‘and Data’ If There’s None?

    Okay, so if Hello Dolly has no data to delete, why did we see that message? In part, this stems from the following idea:

    Delete plugin files AND data

    We wanted it to be more clear as to what was being deleted when you delete, and that was part of a proposed change to WordPress core to tell you if and when database settings are removed on uninstall, and let you leave it alone if needed. Wouldn’t that be nice? Letting you pick which way to go?

    Well. There’s a problem with that dream, and the name of the problem is “Plugin Frameworks.” No, not the CMB2 stuff, I mean the boilerplate plugin frameworks that are oh so popular.

    I hate them. Because they always have an uninstall, and most of the time people leave it alone. That’s right, your brilliant boilerplate will flag an alert that it’s deleting data when it’s not. This doesn’t impact the functionality of the base idea, but it does change the message.

    So Why Does It Say Data?

    Because when you uninstall a plugin, if it was well written, it removes the files and the data.

  • Yoast Custom Meta with CPTs

    Yoast Custom Meta with CPTs

    Remember Monday when I was learning about how I was ignorant of SEO from OnPage?

    Right so here were the odd keyword notes it told me I needed to take care of and I had no idea what was going on. I mean, I looked at this and stared:

    • Add the keyword kima greggs in the meta title
    • Add the keyword kima greggs in the meta description
    • Add the keyword kima greggs to the content of your page
    • Add the keyword kima greggs in the headlines (<h1> to <h6>)
    • Add images and include the keyword kima greggs in the image’s ALT tag

    Then I did what every intelligent person does when faced with an unknown to-do. I read directions.

    The title tag defines the title and is displayed as the page name on the browser tab. The title is very important for search results. It is the heading used to display the search result and is crucial for the ranking.

    Now I actually knew, from having gone through every single tab in Yoast SEO, that titles and metas are handled from the plugin (Yoast > Titles & Meta > Post Types) and, by default, they’re set to be this: %%title%% %%page%% %%sep%% %%sitename%%

    Which translates to: “Pagetitle Number — Sitename”

    Or in the case of Kima, it’s “Kima Greggs — LezWatchTV” (since I never have any numbered pages for those).

    Cool, right? So it was there. Done. I also knew that the name was in the headlines. It wasn’t (and isn’t) in the content of the page, but I’ll accept that SEO hit since contextually it doesn’t work for what I’m writing. Similarly the image thing I handled by having the one image uploaded to the custom post type be the character photo with an alt tag and title of the character name.

    You see how I’m cheating.

    That left me with the meta description and a new question. You see, what I wanted the description to be would be “Kima Greggs, character on the following TV shows: [list of shows]” and what I didn’t want to do was manually type that for 1000 characters. Who would, right? Again, I knew Yoast had a way to customize that!

    Titles and Meta Descriptions for Yoast SEO

    You can see in the above screenshot I already changed the title to remove %%page%% since I know the pages will never be paginated. But the “Meta description template” I needed to address. First is the easy part. I want to document that the character is on a TV show. Fine: %%title%% is a character on a TV show and for TV shows, I could do %%title%% is a TV show and that worked.

    Of course, I wanted to add what stations a TV show was on, which meant I needed to use %%ct_<custom-tax-name>%% which lists the post’s custom taxonomies, comma separated. Except it didn’t seem to pick up things for my custom post types. Turns out this is a bug.

    When I added this: %%title%% is a TV Show on %%ct_lez_tags%% %%sep%% %%excerpt_only%%

    It displayed this: <meta name="description" content="Adventure Time is a TV Show on Cartoon Network &ndash; Adventure Time, c&#039;mon grab your friends. We&#039;ll go to very distant lands."/>

    That was the easy stuff. When I got to characters, it became a lot messier because of how data was stored. To do the basics I looked at what my custom fields were and came up with this:

    %%title%% is a %%ct_lez_sexuality%% %%cf_lezchars_type%% character played by %%cf_lezchars_actor%% on %%cf_lezchars_show%% %%sep%% %%excerpt%%

    Now that should have turned into “Jane is a Lesbian guest character played by Anonymous on Fake Show…” but what actually happened was “Jane is a Lesbian guest character played by Array on Array…”

    I knew why. Those two values are arrays. Which meant I had to come up with some new code.

    function lez_retrieve_actors_replacement( ) {
    	if ( !is_array (get_post_meta( get_the_ID(), 'lezchars_actor', true)) ) {
    		$actors = array( get_post_meta( get_the_ID(), 'lezchars_actor', true) );
    	} else {
    		$actors = get_post_meta( get_the_ID(), 'lezchars_actor', true);
    	}
    	return implode(", ", $actors);
    }
    
    function lez_register_yoast_extra_replacements() {
    	wpseo_register_var_replacement( '%%actors%%', 'lez_retrieve_actors_replacement', 'basic', 'A list of actors who played the character, separated by commas.' );
    }
    
    add_action( 'wpseo_register_extra_replacements', 'lez_register_yoast_extra_replacements' );
    

    What this does is creates a new template variable called %%actors%% and that lists the actors with their names separated by commas (yes, some people have multiple actors, have you even heard of US soaps?). The one for shows is more complicated since it’s a post type referencing another post type (shows and characters are post types), but it’s the same general concept.

    In the end I went with this: %%title%% is a %%ct_lez_gender%% %%ct_lez_sexuality%% %%cf_lezchars_type%% character played by %%actors%% on %%shows%% %%sep%% Clichés: %%ct_lez_cliches%%

    It crams a lot of information into a small place, but it’s also all the important stuff.

  • Torrenting Cache

    Torrenting Cache

    There’s a new cache in town, CacheP2P.

    The basic concept is that you can use BitTorrent to seed your cache across the world, making it even faster for everyone. Setting it up is fairly simple. Configuring it is not. At least not in an automated fashion.

    Traditional web browsing is a direct connection between user and server. Traditional caching is done by having the server (or a proxy of the server) create a static copy of the page and display that. In the case of WordPress and any other dynamic CMS, that works by taking the load off of PHP and MySQL having to generate a new page on every visit.

    By using BitTorrent, this is changed so that you would instead be getting a cached copy not from a server but from someone else’s computer. If you and I were on the same network, I might get the page from you instead of the server. That sounds really weird, doesn’t it? Via two javascript files combine to signal the torrent’s API, and a third file uses the unique page hash to determine freshness. Keep your eye on that last part, it’s what makes the idea of a plugin for WordPress such a pain.

    To get the content for that last file, you have to look at your page in dev tools to grab the security hash:

    [CacheP2P] this page's security hash: (2)
    "c72d19b8ed03be98ceebd06f7c93dc06410b4de4"
    "(http://www.cachep2p.com/api.html)"
    

    On Safari it looks like this:

    Example of what the hash looks like

    Now if it works, and you can see an example on the cachep2p.com domain, it would show results similar to this:

    Example of the cache working

    This did not actually work for me on Safari. At all. It was fine on Chrome, but Safari never served up the cache which is odd.

    My first concern was about cache contamination. That is, if someone downloads the page and messes with it, could they have my site show content I didn’t want it to show? By using hashes, this is minimized. I have a file that defines the valid hashes, and if the copy doesn’t match, it downloads my content, not the bad one.

    However the greater concern is that of accidentally releasing content I shouldn’t. Take this example. I accidentally publish something I shouldn’t, like the plan to tear down the Berlin Wall. Without caching, I can quickly redact it and if Google didn’t scrape my page, it’s like it never happened. With caching (and Google…) the bad content (my destruction plans) remain out there unless I visit the cache provider and flush things. If you’ve ever used a 3rd party proxy like Cloudflare to cache your content, this is the situation when you update your CSS files and have to go force them to refresh.

    With the BitTorrent situation this becomes worse, because the cache is in the hands of the masses. If you were a politician and I your rival, I would have someone constantly visiting your site and saving the cache. Then I could go through it and look for accidental leaks.

    Now of course this could happen today. I could set up a simple content scraper and have it ping your site every so often to save the data. You could, in turn, block my IP, and I would retaliate by setting up a Tor connection to do it from obfuscated IPs. The difference here is that you’re actually encouraging me to cache your data with this plugin.

    An additional concern is the dynamic aspect of WordPress. The only way to grab the hash right now is to view the page. That hash will change when I save a page. In fact, it might change on every page load, in some situations. I didn’t get too far into testing at this point, since I realized that in order for this to work I would have to load a page, grab a hash, edit a file, save that file up on the server, and then it would cache…

    That would be terrible on WordPress. For this to work on any large site, the generation of that hash file would have to be automated. No matter if the site is dynamic or not, to make people manually do that is preposterous. A vaguely WordPress solution I dreamed up was to somehow catch the cache has as the page is saved, store it in a post-meta value, and then use WordPress to generate a ‘fake’ page with the URL and the hash for the cache tool to use.

    It might be easier to do that via something like WP Super Cache of W3TC, and have it save the file as it saves the cached page (and point to the static page instead of the dynamic one) but even then, the rapid changing of WordPress content would make it difficult for a cache to seed far enough out.

    Right now, I think this is something that might only be useful for a small, mostly static, site.