Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

  • Security: Do it the WordPress Way

    Security: Do it the WordPress Way

    Pretty regularly, people complain that I’m being pedantic and stubborn about security. They argue that their home-grown filters and regular expression checks are more than sufficient for sanitizing and validating data. Invariably I tell them “WordPress has a function for that. Please use it. Don’t create your own.”

    Most of the time, this gets a grumbling acquiescence. On the rare occasions it doesn’t I get a pretentious email telling me the developer has been working in tech and computers for 10 to 14 years (10 is most common) and they’ve released code before and they know what they’re talking about.

    You know what? You do. Most of the time the code you people come up with looks fine. But after 14 years working for a bank and around 7 of doing WordPress plugin reviews and nearly 5 of working for a web host, I’ve got a different point of view than you do. I have a mountain of experience that is hard to match. This doesn’t mean I’m the smartest person down the pike, don’t get me wrong. But I’ve seen a lot. I’m like that Farmer’s guy.

    We know a lot because we’ve seen a lot

    With all the things I’ve seen, I’ve developed a very different set of criteria for security beyond just “Is it secure?”

    I know the following:

    • Someone is always going to be smarter than I am.
    • Hackers are incredibly dedicated to being shits.
    • Users are incredibly inventive with usage.
    • People don’t look before they click.

    I ask the following:

    • Is this the fastest (most efficient) way to sanity check the data?
    • Is it being validated to prevent PEBUaK errors?
    • How easy will this be to fix when I find a problem?
    • How much damage will this cause if it breaks?

    To WordPress or Not to WordPress

    When I have a choice of reinventing the wheel or using what WordPress already sanitizes for me, I will always pick WordPress. Every. Single. Time. This is for a very practical reason.

    • People don’t upgrade plugins.
    • People do upgrade WordPress security releases.

    By default everyone using WordPress gets security releases and they get them within 12 hours (more or less). Yes, people can and do disable that, but they’re the minority. When you talk about securing 26% of the Internet, the ability to patch people quickly is paramount. WordPress knows this.

    If I’m using the sanitize_name() function from WordPress and not a hand-hewn regular expression, then if there’s a flaw in that function I know it will be patched and the patch pushed and my users made safe. If I make it myself, I have to pray everyone upgrades.

    Excellence Uses The Right Tools

    Think of it this way. If your plugin becomes a popular plugin and 2% of WordPress users use it, then that’s .5% of all sites on the Internet using your plugin. Which is better for them? Which is safer? Do you rely on the tried and true, tested sanitization via WordPress which will emergency self-update or your own code?

    Once you think of it not as “How can I make my code succeed?” but “How can I build trust for 27% of the internet?” it’s clearer.

    Rely on WordPress and not your own code whenever possible. It’s smarter. It’s safer. It has the long-term view to take you from a newbie to a well-known tool. If there’s a security hole in WordPress, everyone will work to fix it, and it will get out to more people than just your users.

  • Custom AMP Design

    Custom AMP Design

    The basic design of a page using AMP works for news articles. This is by design. It’s meant to be fast to load and fast to display, and that means removing the majority of cruft we shove in sidebars and footers. It means a streamlined website.

    A basic AMP page

    That’s a basic page but it works well for what it needs to be. The issue happens when you consider pages that aren’t your typical ‘news’ pages.

    Compare the information in a character page. On the left is the normal page and the right is the AMP page.

    An example character page with the regular on the left and the AMP on the right

    There’s some work to be done, obviously. Enter Custom Templates.

    Calling the Right Template for the Job

    The example code works great when you want to universally replace things. I don’t. I want to use a different template per-CPT, so my code is this:

    /*
     * AMP custom templates
     */
    add_filter( 'amp_post_template_file', 'lez_amp_post_template_file', 10, 3 );
    function lez_amp_post_template_file( $file, $type, $post ) {
    
    	$post_type=$post->post_type;
    
    	if ( 'post_type_characters' === $post_type || 'post_type_shows' === $post_type ) {
    		$file = get_stylesheet_directory() . '/amp-templates/shows-chars.php';
    	}
    	return $file;
    }
    

    That was the easy part.

    Making Your Template

    Now I need to make the template. It’s actually not as obvious as it might be. You can’t just copy the single.php file from the AMP templates folder and go. No, if you want to use it as-is, you will need to set up how to pull the custom templates.

    For the quick example, we have the Single Template and with that we have to replace all the calls to load_parts. They look like this:

    <?php $this->load_parts( array( 'header-bar' ) ); ?>
    

    We have to change them because the code for load_parts is relative to the file, and it doesn’t call .php so it’s a bit of a drama to call them. I replaced it with this:

    <?php include_once( WP_PLUGIN_DIR . '/amp/templates/header-bar.php' ); ?>
    

    This is pretty much what I tell plugin developers not to do. Never ever use WP_PLUGIN_DIR unless you absolutely have to. Always use plugins_url() and plugin_dir_path() please and thank you. But since I can’t use a URL in include_once() because it’s https) and I can’t use plugin_dir_path() because it’s relative to the file it’s called from, and this is called from the theme. So yes, here I have to use the one constant I tell you not to use. I’m aware of the irony.

    The rest of the template is mostly removing things I can’t filter out. I removed the feature image, I removed the byline (we don’t care who wrote the CPTs, authorship isn’t an issue), and I removed the taxonomies.

    Fix The Images

    Once the main file is loading properly, it’s time to address the two problems with the images: size and location. There’s, thankfully, some default code that can help with this that didn’t need a template. AMP comes with directions on changing your featured image but the example code had to be tweaked in order to handle multiple post types:

    add_action( 'pre_amp_render_post', 'lez_amp_add_custom_actions' );
    function lez_amp_add_custom_actions() {
    	add_filter( 'the_content', 'lez_amp_add_featured_image' );
    }
    
    function lez_amp_add_featured_image( $content ) {
    	global $post;
    
    	$post_type=$post->post_type;
    	$post_id=$post->ID;
    	$image_size = 'featured-image';
    
    	if ( $post_type === 'post_type_characters' ) $image_size = 'character-img';
    	if ( $post_type === 'post_type_shows' ) $image_size = 'character-img';
    
    	if ( has_post_thumbnail() ) {
    		$image = sprintf( '<p class="lezwatch-featured-image">%s</p>', get_the_post_thumbnail( $post_id, $image_size ) );
    		$content = $image . $content;
    	}
    	return $content;
    }
    

    But. This made double images! Why? Because this is a lie: “The default template does not display the featured image currently.”

    It does. This is why I removed the featured image in my template. If I can figure out how not to do that, I’ll be 90% of my way to not needing a custom template at all, because everything else I did in the filter.

    Add the Extra Content

    Remember function lez_amp_add_featured_image() above? Yeah I threw it out and made an insanely complex. I renamed it function lez_amp_add_content() and the if ( $post_type === 'post_type_characters' ) {} section became huge so that I could output the code the way I wanted.

    Filtered content for the AMP pages for characters

    In the image above, you can see I’ve not only added in a lot more meta content from the post, but I also tweaked the CSS to float the image to the left and resize the SVGs. I went super image light on this, loading the smallest image that would work, and the SVGs are very small sized.

    Overall? It should be easier

    The biggest issue I have with this is that I wish I could filter more. If I could turn off the featured images and remove the bylines, then I wouldn’t need the custom template at all. At least, not for the characters. The shows? That’s another matter.

  • OpenGraph Images and Taxonomies

    OpenGraph Images and Taxonomies

    As I worked my way through optimizing the SEO for my Dead Lesbians, I hit a recommendation that I found difficult to handle. It was presented simply. Add an open graph image to the page.

    The problem was the page was a category page (a custom taxonomy).

    Easy: Yoast’s OG Image

    The easiest solution if you’re using Yoast SEO is just to make a custom image and upload it. Go to the taxonomy page, edit it, go to the Yoast section, click on the sharing icon, and you’ll see what needs to be added.

    If you want to automatically handle the open graph image with Yoast SEO, it’s filterable.

    function halfelf_wpseo_opengraph_image( $image ) { 
    	return $image; 
    }; 
    add_filter( 'wpseo_opengraph_image', 'halfelf_wpseo_opengraph_image', 10, 1 );
    

    This means if you have a specific image for a category if the image doesn’t have a post thumbnail, you could do something like this:

    function halfelf_wpseo_opengraph_image( $image ) { 
    	global $post;
    
    	if ( !has_post_thumbnail() ) {
    		if( in_category( 'foo', $post->ID ) ) {
    			$image = get_stylesheet_directory_uri().'/images/cat_foo.png';
    		} elseif( in_category( 'bar', $post->ID ) ) {
    			$image = get_stylesheet_directory_uri().'/images/cat_bar.png';
    		}
    	}
    	return $image;
    }
    

    That’s really bad code, by the way. There are smarter ways than hard coding, especially since you can do some pretty nice stuff with primary categories in Yoast SEO. I would probably have it grab the primary category for the post, force the image, check to make sure the image exists, and have a fallback (just in case someone else added a new category).

    The problem is that the filter doesn’t work on categories and taxonomies because they don’t have the filter set. Posts and pages are fine. Not categories.

    Easy: Adding via wp_head

    Adding in an open graph image via the wp_head action is similarly possible.

    add_action('wp_head', 'halfelf_opengraph_image', 5);
    function halfelf_opengraph_image( ) {
    	echo '<meta property="og:image" content="http://example.com/og_image.png"/>';
    }
    

    This is extendable and you can customize it to be as simple as my example or as complex as pulling a specific image per category or featured image, depending on your theme.

    Not Easy: Reality

    Besides the fact that I can’t use Yoast’s filter for categories, I had a bigger problem. The way I designed my theme, most of my taxonomies had a custom image picked. The images are all SVGs. And you know what doesn’t work on OG images? Yeah, SVGs. Good news was that I had a copy of all the images as a PNG as well, so I was able to use the second method to create default images using the PNG.

    More bad news? I didn’t actually know how to grab the taxonomy ID. While WordPress has a handy function is_tax(), I couldn’t use a global like $post to grab a $tax->ID. Thankfully I could use get_queried_object_id() to set my term ID like this:

    add_action('wp_head', 'lez_opengraph_image', 5);
    function lez_opengraph_image( ) {
    
    	// If it's not a taxonomy, die.
    	if ( !is_tax() ) {
    		return;
    	}
    
    	$term_id = get_queried_object_id();
    	$icon = get_term_meta( $term_id, 'lez_termsmeta_icon', true );
    	$iconpath = get_stylesheet_directory().'/images/png/'.$icon.'.png';
    	if ( empty($icon) || !file_exists( $iconpath ) ) {
    		$icon = 'square';
    	}
    
    	echo '<meta property="og:image" content="'.get_stylesheet_directory_uri().'/images/png/'.$icon.'.png" />';
    
    }
    

    Obviously I know where I’m storing the images. You’ll want to change that for yourself. I went with a fallback image of “square” in case there wasn’t one available, just because I hate having nothing show. Also here I only wanted the custom taxonomies to have this image, not the regular categories and tags.

  • Genericons Neue

    Genericons Neue

    In 2013 I made a silly little plugin called Genericon’d which let you include Genericons on your site in a theme independent way, complete with shortcodes and flexibility for other plugins and themes that might be using it. In 2016, Generico became Genericons Neue.

    The changes were small but huge:

    1. SVG instead of font icons
    2. No more social icons

    The problem I faced was equally small but huge:

    1. How to seamlessly transition from font icons to SVGs
    2. How to handle social!!?!?!

    Thankfully Automattic actually did the hardest work for me, with Social Logos. I can’t design logos. I didn’t want to abandon people. So for me, to be able to just include a second library in the plugin was a fast and easy fix.

    The long and drawn out one was how to make the plugin magically transition. It took me a month, fiddling with it off and on, but as of version 4.0, Genericon’d defaults to using modern SVGs instead of fonts and combines the Genericon Neue icon pack as well as Social Logos to ensure your old code keeps working. If SVGs won’t work for your site, you can either use classic Genericons or the legacy font packs.

    Genericon'd default settings

    Yeah, I gave everyone ‘options’ while still making default decisions. For the most part, no one needs the old legacy stuff unless they’re supporting IE, so this should work right out of the box for everyone, new and upgrades. My only ‘beef’ is that Social Logos doesn’t have a release strategy, so I’m going to have to randomly check for updates.

    A lot of the work I did to figure this out was just testing variations. I knew that by default I wanted everyone to use the minified, super fast SVG sprites, and by default you do. There are hidden options that would let you use the slower images, but I didn’t build out that interface because of the annoying complexity with setting up “if you have Genericons Neue, make sure you don’t have Genericons Classic!” That was a surprisingly large amount of ifs and elses to make it logically flow. I wanted to have it magically flip things over for you, but in the end I went with an alert if the plugin is active and you haven’t selected things.

    You can also make your load even lighter by not including the social icons, but one thing that’s nice about SVGs over Font Icons is that if you’re not using them, there’s no extra load on the site.

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

  • Sortable Custom Columns: Taxonomies

    Sortable Custom Columns: Taxonomies

    You may have noticed that when I was making Sortable Custom Columns I didn’t make it so I could sort some of the fields.

    Showing sorted columns

    That’s because unlike my lovely post meta, those other fields are custom taxonomies. And by their very nature, they’re not sortable. In order to do this however you need some SQL.

    Make the Columns Sortable

    First we have to add our new columns (lez_gender and lez_sexuality) into our sortable function.

    add_filter( 'manage_edit-post_type_characters_sortable_columns', 'sortable_post_type_characters_column' );
    function sortable_post_type_characters_column( $columns ) {
    	unset( $columns['cpt-shows'] ); 			 	// Don't allow sort by shows
    	$columns['postmeta-roletype']		= 'role';	// Allow sort by role
    	$columns['taxonomy-lez_gender']		= 'gender';	// Allow sort by gender identity
    	$columns['taxonomy-lez_sexuality']	= 'sex';	// Allow sort by gender identity
        return $columns;
    }
    

    Last time it only had the unset and the postmeta-roletype columns. Now we’re adding in our taxonomies as taxonomy-{TAXONOMYNAME} to that.

    Actually Sort the Content

    Now here’s the sneaky SQL stuff.

    function lez_gender_clauses( $clauses, $wp_query ) {
    	global $wpdb;
    
    	if ( isset( $wp_query->query['orderby'] ) && 'gender' == $wp_query->query['orderby'] ) {
    
    		$clauses['join'] .= <<<SQL
    LEFT OUTER JOIN {$wpdb->term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
    LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
    LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
    SQL;
    
    		$clauses['where'] .= " AND (taxonomy = 'lez_gender' OR taxonomy IS NULL)";
    		$clauses['groupby'] = "object_id";
    		$clauses['orderby']  = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
    		$clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
    	}
    
    	return $clauses;
    }
    add_filter( 'posts_clauses', 'lez_gender_clauses', 10, 2 );
    

    I repeat this with ‘gender’ changed to ‘sex’ and ‘lez_gender’ changed to ‘lez_sexuality’ for the other taxonomy.

    I tested this on 921 posts in a custom post type and it didn’t make my server cry. Which is a bonus.