Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Query Vars, Post Titles, and Yoast SEO

    Query Vars, Post Titles, and Yoast SEO

    Most people who use WordPress use query vars every single day without knowing. Back in the old days, we used to have websites with URLs like example.com/?p=123 and we called those ‘ugly’ permalinks. This encouraged everyone to make pretty ones, like example.com/2018/post-name/ instead. What many people don’t realize is that those ?p=123 calls are query variables.

    WordPress, and most modern CMS tools, take that pretty permalink, translate it back to a query variable, and go get the post ID with 123. The variable p (which stands for post) has a value of 123. It makes sense.

    And you can make your own.

    Pretty URLs Are Better

    If you have a choice of telling people to visit example.com/?p=123 or example.com/2018/post-name/, I feel confident most of you will pick the latter. And if you’re writing code and you need a consistent location to call, would you rather try to assume that everyone’s using wp-content and look for example.com/wp-content/plugins/myplugin/my-file.php or would you rather use example.com/myplugin/?

    Now. This post is not about how to make those awesome virtual pages. There are some good tutorials like Metabox.io’s to get you started there. This is about when you have a slightly different scenario.

    Using Query Variables in Titles

    For a number of reasons, I have a page built out to manage my custom query calls. This allows me to edit the content of the page like it was, well, a page! Anyone can edit the page content, change the information, and then everything else is magically generated, it can be easily tweaked to fit my theme, and it basically works for me.

    What doesn’t work well is that all the pages have the same title. And that, as we all know, is bad SEO. I don’t want all my pages to have the same title, and it’s confusing for users because they go to example.com/my-thing/sub-thing/ and example.com/my-thing/other-thing/ so logically the page title should be different, right?

    Changing the display title on the page isn’t too terrible. I have this code at the top of the page to grab the query variable:

    $my_thing = ( isset($wp_query->query['my_thing'] ) )? $wp_query->query['my_thing'] : 'main' ;
    

    And then when I echo the title, I do this:

    <h1 class="entry-title">My Thing
    	<?php 
    		$title = ( 'main' !== $my_thing )? ' on ' . ucfirst( $my_thing ) : 's';
    		echo $title;
    	?>
    </h1>
    

    Which means my titles are either “My Things” or “My Thing on That”. If I had multiple ‘words’ in my query variables (separated by a – of course) then I’d use a string replace and capitalize each word with ucwords().

    But Page Titles aren’t Title Titles

    The problem is the page title isn’t the … well … title title. Have you ever looked at your browser bar? Assuming you don’t have 94 tabs open at once…

    A browser with a lot of tabs open, to the point you may have no idea what's in any of them...

    Then your browser would look a little like this:

    A sane number of tabs open, where you can read the title of each tab

    Imagine if they all said “My Things | Half-Elf on Tech”? Well that would suck. It sucks for you reading, it sucks for screenreaders, and if you have Yoast SEO, it’s quite easy to fix.

    Custom Yoasty Variables and Titles

    Step one is to revisit something I did two years ago, making a custom Yoast SEO variable. This time I want to make a variable for %%mything%%:

    wpseo_register_var_replacement( '%%mything%%', array( $this, 'yoast_retrieve_mything_replacement' ), 'basic', 'The type of thing page we\'re on.' );
    

    And the code will be to grab the query variable and parse it:

    function yoast_retrieve_mything_replacement( ) {
    	$my_thing = get_query_var( 'my_thing', 'none' );
    	$return   = ( 'none' !== $my_thing )? 'on ' . ucfirst( $my_thing ) : '';
    	return $return;
    }
    

    Step 2 is going into the page, popping open the snippet editor, and making the title this:

    The Yoast Snippet

    And then? Pour a beer and watch some sports.

  • Conceptualizing Privacy

    Conceptualizing Privacy

    I know a wonderful human named Heather Burns who cares about privacy and GDPR and has made me quite passionate about understanding what the heck I’m talking about. She’s infectious, smart, and well worded. When she talks I listen.

    Earlier this year, she posted her slides from a speaking event, PHP Yorkshire. One of them resonated with me to the point that I keep thinking about it:

    US vs UK/Europe concept of Privacy
    Source: Heather Burns’ PHP Yorkshire Slides

    I sat and read it a few times, and I realized that I absolutely 100% agree with all of the UK/Europe concepts and only one of the US’s. I won’t touch on all of them, but here are the ones I spend a lot of time pondering.

    Ownership vs Freedom

    In the US, there’s a massive misconception that you have a right to say what you want about what you want without consequences. This is absolutely not true. Freedom of speech, in the United states, does not exculpate me from what happens to me after I say a thing. But we have a big bugaboo here about how our freedoms are fundamental rights. So even though the first few Amendments to the Constitution are quite clearly about their direct applications to ‘against the government’ and ‘in a militia,’ people take them, twist them, and make them apply to everything else.

    This runs into an issue with GDPR and people in the UK and Europe, where the law is that you own your own data. You have a right to it, and to what’s said about you. Yeah hang on there. Folks in the US have a right to say what we want. Folks in the UK/Europe have a right to make us shut up.

    That’s working out about as well as you’d think, mostly because we disagree about this other thing…

    Data Ownership

    Really, it should be pretty simple for the freedom of speech to coexist with the right to be private. If I post lies about you, you are legally within your rights in the US to demand I take them down. If I post information about you that wasn’t public, like I know you like burn Beanie Babies (those are stuffed animals, folks), then in the US you’re kind of out of luck unless you can prove it caused you ‘harm.’ Across the pond? I have to delete it.

    And right there, I agree with the Europeans. If I take privileged information and make it public, I’m a horrible human first of all. I’ve betrayed your trust, and I’ve probably done it for financial gain. On the other hand, if I take public information (like a photo of you from the Associated Press of you burning a Beanie Baby in Central Park) and share it, I’m still a pretty horrible human, but not in the same way.

    As a human, I think I should have the right to own my own data. But this comes with a measure of responsibility. In other words, I’m responsible for what I put out there. If I make it public that I’m a lesbian (which I did), am I legally allowed to demand you remove all references to me being one on your site? In other words, do I get take-backs if I make things public?

    Maybe, but over yonder, I should at least ask first!

    Cooperation Before Court

    There’s a concept called “Assume good faith” and it’s one of Wikipedia’s fundamental principles. It’s related to the concept that we should never attribute to malice that which can be ascribed to ignorance. Generally this comes up when I talk to people about copyright or trademark violations. I never assume people meant to violate those things, just that they were unaware of things.

    The idea that someone has to ask me to remove a thing before suing me would be a lovely thing. The closest I can think of in the US is the way DMCA requests are handled. That is, I can issue a counter notice and either state “Hey, removed it!” argue back that it’s fair use. But that isn’t the same as the idea that we should talk before we go to lawyers. And that’s, you know, respectful.

    I spend a lot of time thinking about this based on two other sites I run, where there is personal information of other people. It’s all public-personal information, but in general if someone asks me to remove data, I’ve complied. There was one instance where I didn’t, and I explained why not and the other person agreed it was a fair representation of the situation.

    What Happens Now?

    Well. A lot of confusion and arguments about who has the right to what and where and when.

    There’s going to be a lot of change in your future.

  • Caching Dismissible Alerts With localStorage

    Caching Dismissible Alerts With localStorage

    There are a lot of reasons to want to have a temporary, but dismissible, alert on the front end of your website. And there are a million different ways to do that. The trick is … how do you do that and make your site remain cacheable?

    Web Storage (Maybe) Is the Answer

    I preface this with a warning. I’m going to be using Local Storage, and that is not something I generally advocate. Web storage (aka DOM storage) is the practice of storing data in the browser. You can do this in a few other ways, such as sessions or cookies. But both of those can be detrimental to caching. After all, PHP Sessions and cookies tell systems like Varnish and Nginx “Don’t cache this.” They’re also persistent, so if I get a cookie once, I keep it until it expires or I delete it.

    On the other hand, web storage is outside the website entirely, that is my server has no idea about them, and it doesn’t really impact caching at all. Varnish, Nginx, and all those sorts of server-based services don’t care about it, and if implemented properly, there’s no problem. You can store the data locally or per-session, which means ‘forever’ or ‘for as long as this browser window is open.’

    Don’t Use localStorage (Most of the Time)

    That all sounded awesome. So then why are there so many articles advocating you not use localStorage? Well there are some massive caveats:

    1. It’s pure javascript, so PHP doesn’t easily get it
    2. If you store sensitive data in it, you’re a numpty
    3. It can ‘only’ store 5 megs
    4. The content has no expiration
    5. You can only use string data
    6. All your localStorage is loaded on every single page load
    7. Any other javascript can see it and play with it

    That’s starting to sound squidgy, right? Randall Degges has a good writeup of the drawbacks.

    Well good news here. This use is possibly the only time I’ll ever advocate it’s use. I’m going to us it here because it works with caching, it works with most browsers (IE8+), and the worst case scenario here is that people will always see my alert.

    Pull Up With Bootstrap

    I’m using Bootstrap on this particular site, and it makes my life hella easy because they built in dismissible alerts. But the gotcha? Those alerts aren’t persistent. Which means if I want an alert to go away forever, then I’m SOL.

    Except I’m not. Bootstrap includes a way to run an ‘action’ on dismiss, which means I could do something like this:

    jQuery(document).ready(function($) {
        var gdpr = localStorage.getItem('gdpr-alerted') || '';
    
        if (gdpr = 'yes') {
            $('#myAlert').alert('close');
        }
    
        $('#myAlert').on('closed.bs.alert', function () {
           localStorage.setItem('gdpr-alerted','yes');
        })
    }
    

    What this does is it sets a variable (gdpr) based on the content of my item in localStorage (gdpr-alerted). If that value is yes, it closes the alert. Otherwise, it sets it to yes when someone closes the alert.

    That actually works just fine, but it has a weird blip if the javascript is loaded in the footer, where you would see my alert for a split second before the page finished loading. So I decided to go another way, and factor in some expirations.

    The (GDPR Related) Code

    First up the PHP:

    function my_gdpr_footer(){
    	if ( !is_user_logged_in() ) {
    		?>
    		<div id="GDPRAlert" class="alert alert-info alert-dismissible fade collapse alert-gdpr" role="alert">
    			This site uses cookies for stuff. Keep browsing and we keep tracking. Wanna know more? <a href="/terms-of-use">Read our Terms of Use</a>.
    			<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    		</div>
    		<?php
    	}
    }
    add_action( 'wp_footer', 'my_gdpr_footer', 5 );
    
    function my_scripts() {
    	wp_enqueue_script( 'bootstrap', get_template_directory_uri() . '/inc/bootstrap/js/bootstrap.min.js', array( 'jquery' ), '4.1.1', 'all', true );
    	wp_enqueue_script( 'lwtv-gdpr', get_template_directory_uri() . '/inc/js/gdpr.js', array( 'bootstrap' ), '1.0', 'all', true );
    }
    add_action( 'wp_enqueue_scripts', 'my_scripts' );
    

    Now my sneaky JS:

    // jQuery dismissable - sets localStorage a year
    
    jQuery(document).ready(function($) {
    	var gdpr = localStorage.getItem('gdpr-alerted') || '';
    	var year = new Date().setFullYear(new Date().getFullYear() + 1);
    
    	if (gdpr < new Date()) {
    		$('#GDPRAlert').addClass('show');
    		localStorage.removeItem('gdpr-alerted');
    	}
    
    	$('#GDPRAlert').on('closed.bs.alert', function () {
    		localStorage.setItem('gdpr-alerted',year);
    	})
    });
    

    The reason this works is that I remove the show class from the PHP and by default assume it shouldn’t show. Then in the javascript, I set up the value as ‘a year from now’ instead of ‘yes’, and check. If the localStorage value is less than ‘now’, then it’s expired and we should show the class (and delete the storage). Otherwise, it’s the same old same old.

    But Should We?

    That’s the big question though. Is this the best way to go about it? The next best choice would be IndexedDB, however that is not supported by ‘enough’ browsers yet, so yes. For now, this is the best choice.

  • How Many Plugins Is Too Many To Create?

    How Many Plugins Is Too Many To Create?

    That wasn’t the way you expected that title to end, I bet. You were thinking “How many plugins is too many to have on my site” and that’s absolutely not the topic here. No, instead I’m asking how many plugins is too many for a developer to create?

    I Got 99 Problems …

    I think that plugins should be specific. That is, I’m not a fan of a conglomerate of plugins like Jetpack, that do a little of everything. Instead, I like a plugin that does the thing it’s supposed to do, preferably simply and well, and it moves on. That means I often have 20-30 plugins installed on a site, and that’s okay.

    At the same time, as a developer, having to support 20-30 plugins is a drain on my limited resources. Becuase here’s what I have to do:

    1. Keep up with all core changes
    2. Include and test all library updates
    3. Test with every release
    4. Update my readme
    5. Review reviews and support posts to make sure I’m not missing things

    Multiply that by 20 and it’s a lot of work. And is that work I feel like I must do?

    Gimmie One Reason …

    The reality of having plugins for WordPress, or any add on for any project, is that it’s generally thankless work and you will have more bad days than good. That’s true of many things in life, and as depressing as it can be, it’s important to keep an eye on the reality of the situation. 

    Developing software is very analytical art. You create something out of nothing, you design and test and change and tweak, and then present it to the world. Of course those days when people tell you “I don’t like the color” suck, but being humans, we discard that and grab on to the days when someone says “I love the carrot!”

    And the reality of the question at hand isn’t how many is too many, but how many are worth the work and the little reward?

    Bring it Together …

    Lately I’ve been advocating something different. Instead of making 13 separate types of gallery plugins, I’ve suggested people make one plugin for galleries and include those 13 types as display options. The amount of work is roughly the same, but it means I only have one plugin to manage instead of 13 separate readme files to edit and installs to spin up. I also have one place to look for any support posts or reviews.

    Obviously this doesn’t always work. Sometimes you have to split things up. There’s little point it combining a WooCommerce plugin with a NextGen Gallery one (unless the plugin is implementing NextGen with Woo products…). But if you can connect your projects by type, you may find out that there’s crossover. Instead of spreading your user base out over 10 plugins, you can keep them manageable with 5 to 8.

    Working For The Man …

    And what about Jetpack? It’s effectively XX separate types of plugins:

    • Writing
    • Sharing
    • Discussion
    • Traffic
    • Security

    Except when you look at that, it suddenly all connects. When I write I want to share and I want people to discuss. I also want to keep an eye on my traffic and being secure…. Okay that last one might be better off on it’s own, but it’s a suite of related apps. 43 separate apps, but they are all related when you get down to it.

    Which means even if you’re making a plugin for your company, you can probably combine it with other things safely. And that means less access and security concerns for you too, as you only have to keep track of who has access to one plugin instead of 50.

    How Many Is Too Many?

    This is as subjective as all get out, but I’ll say this. Once you personally support 20 plugins for WordPress, take a good hard look at how much time you’re spending and ask yourself… is it worth it?

    It’s okay to say no.

  • Future Proof Names

    Future Proof Names

    The other day I was talking to someone about the name of their plugin.

    No, not about copyright and infringement, though that comes up a lot. I was talking to them about the meaning of the plugin name. They wanted to pick a name that was memorable, meaningful, and descriptive. I wanted them to drop the last one. Or rather, to reconsider the last one.

    Names Aren’t Descriptions

    My name, Mika, is not a description, it’s an identifier. Even my handle, Ipstenu, is an identifier. My domain ‘HalfElf.org’ actually is a bit of a descriptor, but it’s also an identifier. I am a Half Elf Rogue to many people, and that’s as it should be. But a descriptor is “Professional Lesbian” not “Half Elf on Tech” and yes, this matters.

    A good name is memorable (check), meaningful (check), and descriptive without being a description. Because a name is how we identify and remember the weird tech site we went to, versus the tools we use.

    In so far as plugins go, however, you have one more thing to think about, and that’s the plugin slug. The slug is like this post’s URL. Changing it comes at a cost and in the case of plugins, it’s impossible. So while I’m content to allow people to pick some silly names, I’m pretty sticky about the plugin url.

    Woo(Commerce) There It Is

    Whenever people submit plugins named “WooCommerce Thingy” they find their slugs changed to woo-thingy instead of the expected woocommerce-thingy because, well, they’re not WooCommerce. This sometimes incurs their ire, generally because they didn’t read the FAQ, and I’ve become resigned to linking them back to it. Most of the time, people just go “Oh, okay I get it.” and move on. Sometimes when the slug is less easy to ‘correct’ for them, like if they call a plugin “Google Fast Typing” (slug google-fast-typing) I have to email them and sort out a new name (probably “Fast Typing for Google” unless “Fast Typing” is trademarked by the Googs).

    Like I mentioned before, I don’t really push too much about a display name. You can change it as many times as you want, and coming back in a month to remind you “Hey, if you’re not Google, you shouldn’t start your plugin display name with ‘Google.’ They may get snarky at you.”

    But the display name is also very much abused. People use it as an extra ‘short’ description, which really only goes to annoy people who see “Luxembourg – A new plugin that will solve all your woes!” as a plugin name. Thanks. But that’s what the description is for.

    Gutenbye-bye-bye

    Gutenberg is a very popular plugin to make an add-on for these days. And a lot of people want to name their plugins things like “Joe’s Author Blocks for Gutenberg” which nets them the slug joes-author-blocks-for-gutenberg and I don’t know about you, but I find that excessive.

    See, you don’t want to have “Gutenberg” in your plugin slug at all. Unlike WooCommerce (or Google, or Facebook), we know Gutenberg is going to go away. Remember, the goal of Gutenberg is to be in WordPress 5.0!

    So that means naming your plugin author-blocks-for-gutenberg is shortsighted. In another four years, will anyone remember Gutenberg was the project name? Quick way to check, ask people at a local meetup “What’s MP6?” and see who knows.

    Name Better

    Think about the future of your plugin, the project, and the related items. Locking yourself into a name you regret later is one thing, but since a display name can be anything, you can name your plugin anything! Making a feature rich Gutenberg add on? Manutius is a great name! Changing the look entirely? Call it Reformation. Making a simple author block editor? halfelf-author-blocks is an okay slug, but “Block Your Authors” sounds pretty bad.

    Just remember, it’s eye before flea, except after sea.

    Someone carving hieroglyphics, when someone reminds him "It's eye before flea, except after sea."
  • It’s Just Math: WP-CLI Edition

    It’s Just Math: WP-CLI Edition

    Remember how I talked about doing math on a post when it was saved?

    Well what if I wanted to run that at another time? Like what if I knew I needed to update one show and I didn’t want to go in and save the post?

    I can to this with WP-CLI:

    WP CLI Magic

    class WP_CLI_MySite_Commands extends WP_CLI_Command {
    	/**
    	 * Re-run calculations for specific post content.
    	 * 
    	 * ## EXAMPLES
    	 * 
    	 *		wp mysite calc actor ID
    	 *		wp mysite calc show ID
    	 *
    	*/
    	
    	function calc( $args , $assoc_args ) {
    
    		// Valid things to calculate:
    		$valid_calcs = array( 'actor', 'show' );
    		
    		// Defaults
    		$format = ( isset( $assoc_args['format'] ) )? $assoc_args['format'] : 'table';
    
    		// Check for valid arguments and post types
    		if ( empty( $args ) || !in_array( $args[0], $valid_calcs ) ) {
    			WP_CLI::error( 'You must provide a valid type of calculation to run: ' . implode( ', ', $valid_calcs ) );
    		}
    
    		// Check for valid IDs
    		if( empty( $args[1] ) || !is_numeric( $args[1] ) ) {
    			WP_CLI::error( 'You must provide a valid post ID to calculate.' );
    		}
    
    		// Set the post IDs:
    		$post_calc = sanitize_text_field( $args[0] );
    		$post_id   = (int)$args[1];
    
    		// Last sanitity check: Is the post ID a member of THIS post type...
    		if ( get_post_type( $post_id ) !== 'post_type_' . $post_calc . 's' ) {
    			WP_CLI::error( 'You can only calculate ' . $post_type . 's on ' . $post_type . ' pages.' );
    		}
    
    		// Do the thing!
    		// i.e. run the calculations
    		switch( $post_calc ) {
    			case 'show':
    				// Rerun show calculations
    				MySite_Show_Calculate::do_the_math( $post_id );
    				$score = 'Score: ' . get_post_meta( $post_id, 'shows_the_score', true );
    				break;
    			case 'actor':
    				// Recount characters and flag queerness
    				MySite_Actor_Calculate::do_the_math( $post_id );
    				$queer = ( get_post_meta( $post_id, 'actors_queer', true ) )? 'Yes' : 'No';
    				$chars = get_post_meta( $post_id, 'actors_char_count', true );
    				$deads = get_post_meta( $post_id, 'actors_dead_count', true );
    				$score = ': Is Queer (' . $queer . ') Chars (' . $chars . ') Dead (' . $deads . ')';
    				break;
    		}
    
    		WP_CLI::success( 'Calculations run for ' . get_the_title( $post_id ) . $score );
    	}
    }
    

    What the What?

    The one for shows is a lot simpler. I literally call that do_the_math() function with the post ID and I get back a number. Then I output the number and I’m done. If I wanted it to run for all shows, I could use WP-CLI to spit out a list of all the IDs and then pass them to the command one at a time. Or I could write one that does ‘all’ posts. Which I may

    But the point is that I now can type wp mysite calc show 1234 and if post ID 1234 is a show, it’ll run.