Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

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

  • jQuery Tablesorter

    jQuery Tablesorter

    So you want to sort tables on your WordPress site, without learning a whole mess of code.

    Good news. There’s a plugin called Table Sorter that can do this and in pretty much just works. Except… There are cases where you’re outputting data in a theme (or a plugin) and you can’t use that plugin to do it.

    Don’t panic, we’ve got you.

    Enqueue The Right Tablesorter

    I’m aware there’s a tablesorter.com – Don’t use it. It’s out of date at the site is possibly hacked. Instead, grab Tablesorter from RobG (aka mottie). Rob is still updating this plugin and debugging it, so it’s a better bet that the various other forks.

    You’ll enqueue this the ‘normal’ way:

    wp_enqueue_script( 'tablesorter', plugins_url( 'jquery.tablesorter.js', __FILE__ ), array( 'jquery' ), '2.30.5', false );
    

    There’s a second part to the enqueue though. You see you also need to tell it what to sort. That is, tell the javascript what to pay attention to.

    That’s done by using a class and an ID: <table id="myTable" class="tablesorter"></table>

    If you’re using the plugin I mentioned above, you only have to do the latter, because it accounts things differently but, properly, you should be using the ID. Then you have to insert this javascript:

    $(function() {
      $("#myTable").tablesorter();
    });
    

    Which is actually wrong. For WordPress. Again, no panicking!

    jQuery(document).ready(function($){
      $("#myTable").tablesorter();
    });
    </script>
    

    See? That was easy. If you wanted to be more WordPressy, you do this:

    wp_add_inline_script( 'tablesorter', 'jQuery(document).ready(function($){ $("#nationsTable").tablesorter(); });' );
    

    You were expecting more?

    That’s really it. I do some extra weird stuff, since I call it on one page only (statistics) and that pages uses query variables so you can have /statistics/nations/ without me needing to make multiple sub pages, and it looks like this:

    	function enqueue_scripts() {
    
    		if ( is_page( array( 'statistics' ) ) ) {
    			$statistics = get_query_var( 'statistics', 'none' );
    			wp_enqueue_script( 'tablesorter', plugin_dir_url( dirname( __FILE__ ) ) . 'assets/js/jquery.tablesorter.js' , array( 'jquery' ), '2.30.5', false );
    			wp_enqueue_style( 'tablesorter', plugin_dir_url( dirname( __FILE__ ) ) . 'assets/css/theme.bootstrap_4.css' );
    
    			switch( $statistics ) {
    				case 'nations':
    					wp_add_inline_script( 'tablesorter', 'jQuery(document).ready(function($){ $("#nationsTable").tablesorter({ theme : "bootstrap", }); });' );
    					break;
    				case 'stations':
    					wp_add_inline_script( 'tablesorter', 'jQuery(document).ready(function($){ $("#stationsTable").tablesorter({ theme : "bootstrap", }); });' );
    					break;
    			}
    		}
    	}
    

    Oh right that also demonstrates a theme!

    Tablesorter lets you use themes like Bootstrap 4.x so your tables can be all matchy-matchy.

    But at this point, it should be enough to get your tables sorted.

  • Bad Faith Names

    Bad Faith Names

    One of the things about Open Source is we can name things whatever we want. This comes with a great amount of responsibility though, since we have to both come up with unique, memorable names that make sense and respect everyone else.

    Respect is a funny thing with names. For example, in order to respect my friend Tracy, I wouldn’t name my company LYKES Inc, because that would be very similar to her company of YIKES Inc. But also I know she’s trademarked the domain, which is a smart choice, and that means I have to respect her trademark as well.

    Speaking of Trademarks

    When it comes to trademarks, everything’s a little messier too. 

    This isn’t about not naming your plugin “Google Analytics.”

    This is about when you own a trademark and people are infringing on it, and how you can chose (or nor) to behave.

    This is about being cocky.

    There’s no other way to explain this, but a romance novelist trademarked the word ‘cocky.’

    No, this isn’t a joke. Since 2015, for a number of reasons, the word ‘cocky’ has been super popular with romance authors, and one of them decided to trademark the word. In 2018 she applied for, and got, a trademark on the word. Not just the word mark (which is like Pepsi’s trademark on the word and the font), but also the actual word cocky, as used in romance novels.

    And then she did exact what you’re thinking, and she decided to sue everyone else who was using it.

    Trademark Bullying

    Fallen Hopkins said her reasoning was her users. “I receive letters from readers who lost money thinking they bought my series. I’m protecting them and that’s what trademarks are meant for.”

    When you hear it that way, it does sound a little sensible, doesn’t it? She wanted to help her readers be less confused that “The Cocky Cowboy” isn’t a book in her series “The Cocky Series” (in which there is a book called “Cocky Cowboy”). She kicked the author of “The Cocky Cowboy,” who renamed her book “The Cockiest Cowboy To Have Ever Cocked” and now I’m a little in love.

    Now most of the time you can’t actually do that! I mean, I could name a book “Catcher in the Rye” if I wanted to, because you can’t copyright book or story titles. What you can do is the title of the book as it pertains to non-book goods and services, as long as the goods aren’t the book. With a trademark, if I have a book series, I can trademark the series name (see “Harry Potter and …”), but not a single individual title. Until I make a movie.

    But more to the point here, Hopkins was being a damn bully by deciding she was going act in bad faith.

    Yes. It’s legal, but it’s bad faith.

    Bad faith is simply you doing something that is legal but you know it’s the bad thing to do.

    That’s not a legal definition, by the way. If you look it up in a law dictionary, it involves the intent to deceive, which is a weirder thing. The real question is why is this legal? Right? Why would someone possibly be able to trademark cocky!?

    Turns out, it’s actually not hard to trademark a common word if you do it right. Take Apple, for example. You know, Macintosh the company? apple.com? Right, they trademarked Apple, but only as it relates to computers. I can name my car company Apple Cars if I wanted, but I better keep away from self-driving cars, eh?

    There’s a catch to all this. If you’re in the USA, you may be aware of the First Amendment. You know the one? Well there’s a doctrine about all this that basically exists to stop trademark law from stomping all over our rights. People build careers on this stuff, so the short version for you is that folks who are chapped about this have a damn good case against her doing this maliciously, and getting the trademark overturned.

    The problem is they need lots of money, which they don’t have. We’re talking about a bunch of indie e-book authors, after all. They may not have money but they have the internet, and they’ve been using it to savagely take down Hopkin’s reputation.

    You really should never piss off people who are good with words.

    Cockygate Doesn’t Hold Up

    The good news in all this is the trademark’s being canceled. The bad news is that someone else with deeper pockets probably has a great idea now and is going to be an even bigger problem for people later on.

    People will get confused. People can’t even tell differently named web hosts apart, so of course someone will think “Joe’s Google Analytics for Sports Sites” is an official Google plugin on WordPress.org (seriously someone did). They just don’t read and think, and all the trademark protection in the world isn’t going to help them out.

    But think about how you’re approaching this. Ask people to change the display name of things, and ask them to make sure it’s clear they’re not related to you. And when someone gets confused, point out “That plugin/app doesn’t have my trademark’d logo, so you can see it’s not mine. Sorry about the confusion, here’s mine.”

    If you’re interested, read Vox’s explanation on cockygate and please, don’t be a cock when you’re protecting your trademark.