Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Organization

    Organization

    In September 2005, Lorelle wrote what I consider to be the definitive piece on tags vs categories. In 12 years, my opinions have not changed and I still feel her explanation is correct. That said, there is room for improvement at scale.

    The Gist

    Her advice boils down to this:

    • Categories are a table of contents
    • Tags are index words

    By this we mean that categories are the high-level, big ticket items, and tags are the smaller, more precise terms. This is, I feel, the heart of understanding the two.

    Further down, Lorelle states that at around 25 posts, a tag is ‘big enough’ to be a category, and that if a category dominates a blog, it should perhaps be a separate blog. And that’s where I disagree.

    On Beyond Zebra

    When she wrote her post, the concept of custom taxonomies was barely a gleam in someone’s eyes. Multisite was still WPMU, and a separate installation. Today we have the ability to add our own taxonomies (either in category or tag styles) and we can create a network of related sites on our own. All we need is a little more technical know-how.

    When we add on custom taxonomies, we afford ourselves a new way to classify posts, so to the above I would add this:

    • Custom Taxonomies are critical but exceptionally unique index words that must be grouped together

    Okay that was long, I know, but a Custom Taxonomy is in essence a new subdivision of your site. You can either make it a new table of contents or a new index … or a combination of the two. It’s a little wild, especially when you factor in custom post types.

    Overwhelming Category? Custom Post Type!

    Instead of making a new blog when your category gets too large and unwieldy, I would recommend making a new custom post type. If I use my helpful example of LezWatchTV, we currently have three custom post types: Shows, Actors, and Characters.

    While we could have made them into posts, and used categories to index them, having them be their own post type means instead of a table of contents, I’ve made an appendix. This gives me access to all the cool WordPress features, like archives and sorting and organization, but it does so outside the realm of posts which restricts crossovers. Unless you’re really clever with cross-related content.

    A custom post type keeps it all on one blog, but separates them like your laundry.

    Too Many Tags? Custom Taxonomy!

    If you find yourself having too many tags, it’s time to consider a custom taxonomy. Again, pointing to LezWatchTV, actors have two custom taxonomies: gender identity and sexuality. While those are the same as we use for characters, by having them separate and only applicable to the actor post type, we are able to give a list of all trans female actors with a click. In other words, we’re using WordPress’s native features.

    But if we look at the custom post type for TV shows, we have a lot more taxonomies, including two that are constantly being added on to: nations and stations. Every time a new station airs a show, we have to add it in. And there, as of April 1, we end up having 29 nations and 168 TV stations.

    Which brings up the next problem, and one that Lorelle does indeed address, but not the way I would.

    When Tags Go Rogue

    Can tags still go too large? Yes. Oh my lordy, yes.

    Recently I saw a site that used unique tags on every single post. I physically flinched when I realized that.

    You see, they had around 30,000 posts and 48,000 tags, and for the life of me I couldn’t understand why until I read the site and looked. For every single post there was a commensurate tag for the post title and the date. After 365 dates they thankfully started to repeat, so you might have 10 posts for the march-25 tag. Except they weren’t consistent and someone else used 25-march and now you can see the rabbit hole fall into infinity and beyond.

    Now that said, I have 168 tags for TV stations, each TV show has one, maybe two if they’re lucky or weird, and some tags only have 1 show listed. Others, like ABC, NBC, and CBS, have around 60. Do I think any of those are ‘too large’?

    I don’t. Because the number of 25 posts to a tag only holds up at a smaller scale. With 100 to 200 posts, yes, that starts to make sense. At 600 to 3000 posts, suddenly having 198 posts tagged with “Bury Your Queers” doesn’t sound so out of place. It’s about the percentages, somewhat, and also the use-case.

    If I know people are looking for a smaller tag (say they really want to see the 10 shows that have the ‘Fake Relationship’ tag), then for the purpose of this site, it’s important. On the other hand, if only one character was tagged cougar, I might not keep the tag as it’s too small to make the data useful.

    Optimal Organization

    There is no magic number of tags to categories to custom post types to taxonomies. It all comes down to understanding the goal of your site, the way users look for data, and what is maintainable to you.

    In the case of the site with 48k tags, I would have them delete all the date ones, as well as the ones with the same names as posts, and stick to using topical tags. After all, if a tag is only used once, or duplicates some feature already found in WordPress, it’s perhaps not the best idea.

  • WP-CLI Tables

    WP-CLI Tables

    I was working on an update to the Varnish plugin I’ve adopted, and one of the requested features was for more debugging tool. I’d added in a status page, but this plugin is also used by web hosts, and sometimes asking a customer “Can you go to this bad and send me the results?” is a time sink.

    So why not add in a command line tool?

    WP-CLI?

    I love WP-CLI. It’s a command line interface (CLI) for WordPress (WP) that lets you do most anything via the command line. You can install and activate plugins, update themes, even write posts and add users. But if you’re tech savvy, it’s also a great tool to automate and manage the minutia of WordPress maintenance drudgery.

    I’d already built out a basic varnish flush command (wp varnish purge) so adding on to it isn’t terribly difficult. But what was difficult was making the output what I wanted.

    Start With an Array

    No matter what you need an array of the correct format for this to work. I was already storing everything in an array I save in a variable called $results that looks like this:

    Array ( 
        [varnish] => Array ( 
            [icon] => awesome 
            [message] => Varnish is running properly and caching is happening. 
        ) 
        [remote_ip] => Array ( 
            [icon] => awesome 
            [message] => Your server IP setup looks good. 
        ) 
        [age] => Array ( 
            [icon] => good 
            [message] => Your site is returning proper "Age" headers. 
        )
    )
    

    I was initially doing this so I could loop and output all results with an icon and message on the status page, but translating this to wp-cli was a matter of taking the array and repurposing it.

    WP-CLI Tables

    In order to add in a table output to WP-CLI, you use the format_items function:

    WP_CLI\Utils\format_items( $format, $items, $headers );
    

    The $format value is taken from $assoc_args['format'] (I set mine to default to table if it’s not defined). The $items are your array, and the $headers are another array of what your headers are.

    This is the tricky part. You have to make sure your array of headers matches your array of items, and fulfills the output you desire. In order to do this, start with figuring out what you need to output.

    In my case, I wanted a name, a status (aka the icon), and the message. This means my array looks like this: $headers = array( 'name', 'status', 'message' )

    Rebuild The Array

    Once I sorted out what the format was like, based on the headers, I built the items array as follows:

    // Generate array
    foreach ( $results as $type => $content ) { 
    	$items[] = array(
    		'name'    => $type,
    		'status'  => ucwords( $content['icon'] ),
    		'message' => $content['message'],
    	);
    }
    

    Remember, my $results were already an array. I’m just making it look right here.

    Final Results

    How does it look? Like this:

    +-----------+---------+-------------------------------------------------------+
    | name      | status  | message                                               |
    +-----------+---------+-------------------------------------------------------+
    | varnish   | Awesome | Varnish is running properly and caching is happening. |
    | remote_ip | Awesome | Your server IP setup looks good.                      |
    | age       | Good    | Your site is returning proper "Age" headers.          |
    +-----------+---------+-------------------------------------------------------+
    

    And that is a nice tool for people to debug.

  • Shortcode Example: Reviews

    Shortcode Example: Reviews

    Sometimes people want to have reviews mean ‘people leave reviews on my work.’ But the other kind of reviews are the ones where I review other peoples’ works. And for that, I found it helps to have some kind of standard.

    Let’s say I’m reviewing a TV show for overall quality but also overall gayness. That is, I want to be able to write up a post and then, at the bottom, put up a shortcode to say “This show is really good but has no gay characters.” To do that, I made a list of the important factors to distill:

    • Name: The name of the TV show
    • Summary: A short, 140 character summary of the overall show.
    • Queer: A 1-5 rating of how queer the show is
    • Rating: A 1-3 scale (yes, meh, no) for how good the show is overall
    • Warning: Is there a trigger warning (or CW) to be aware of

    The intent is to make it easy for someone to scroll down and find what they want to watch. Right?

    The Code

    A bit of warning. There are two specific to my site bits of design in here. First are the icons. While I’ve generalized them for you as Emoji, keep in mind you probably want to have your own style here. Second, I’m using Bootstrap, so I’ve leveraged some of their default code. You’ll want to tweak the CSS.

    class TV_Shortcodes {
    
    	/**
    	 * Constructor
    	 */
    	public function __construct() {
    		add_action( 'init', array( $this, 'init' ) );
    		add_filter( 'widget_text', 'do_shortcode' );
    	}
    
    	/**
    	 * Init
    	 */
    	public function init() {
    		add_shortcode( 'review', array( $this, 'review' ) );
    	}
    
    	/**
    	 * Reviews.
    	 */
    	public function review( $atts ) {
    
    		$attributes = shortcode_atts( array(
    			'title'   => 'Coming Soon',
    			'summary' => 'Coming soon ...',
    			'queer'   => '3',
    			'rating'  => 'meh',
    			'warning' => 'none',
    		), $atts );
    
    		$queer = (float) $attributes['queer'];
    		$queer = ( $queer < 0 )? 0 : $queer;
    		$queer = ( $queer > 5 )? 5 : $queer;
    
    		$worth = ( in_array( $attributes['worth'], array( 'yes', 'no', 'meh' ) ) )? $attributes['worth'] : 'meh';
    		switch ( $worth ) {
    			case 'yes':
    				$worth_icon = '??';
    				$worth_color = 'success';
    				break;
    			case 'no':
    				$worth_icon  = '??';
    				$worth_color = 'danger';
    				break;
    			case 'meh':
    				$worth_icon  = '?';
    				$worth_color = 'warning';
    				break;
    		}
    
    		// Get proper triger warning data
    		$warning = '';
    		$trigger = ( in_array( $attributes['trigger'], array( 'high', 'medium', 'low' ) ) )? $attributes['trigger'] : 'none';
    
    		if ( $trigger != 'none' ) {
    			$warn_image    = '⚠️';
    			switch ( $trigger ) {
    				case 'high':
    					$warn_color = 'danger';
    					break;
    				case 'medium':
    					$warn_color = 'warning';
    					break;
    				case 'low':
    					$warn_color = 'info';
    					break;
    			}
    
    			$warning = '<span data-toggle="tooltip" aria-label="Warning - This show contains triggers" title="Warning - This show contains triggers"><button type="button" class="btn btn-' . $warn_color . '"><span class="screener screener-warn ' . $warn_color . '" role="img">' . $warn_image . '</span></button></span>';
    		}
    
    		$output = '<div class="bd-callout"><h5 id="' . esc_attr( $attributes['title'] ) . '">Screener Review on <em>' . esc_html( $attributes['title'] ) . '</em></h5>
    		<p>' . esc_html( $attributes['summary'] ) . '</p>
    		<p><span data-toggle="tooltip" aria-label="How good is this show for queers?" title="How good is this show for queers?"><button type="button" class="btn btn-dark">Queer Score: ' . $queer . '</button></span> <span data-toggle="tooltip" aria-label="Is this show worth watching? ' . ucfirst( $worth ) . '" title="Is this show worth watching? ' . ucfirst( $worth ) . '"><button type="button" class="btn btn-' . $worth_color . '">Worth It? <span role="img" class="screener screener-worthit ' . lcfirst( $worth ) . '">' . $worth_icon . '</span></button></span> ' . $warning . '</p>
    		</div>';
    
    		return $output;
    
    	}
    }
    new TV_Shortcodes();
    

    The Future

    I’m thinking about changing the scores from numbers to stars, and adding in a link if the show has been added to the site. But it being a shortcode, it’s reasonably extensible.

    Enjoy, and export to your own review sites.

  • Genesis Theme: Anonymize Posts

    Genesis Theme: Anonymize Posts

    Actually, I anonymize my pages.

    See, I have multiple authors on a site, and one of the things I like is to celebrate them when they post. Awesome. But I don’t want to highlight who wrote the pages on the site. Or who posted the videos. Those are informational and don’t need any ego attached.

    When using StudioPress’ Genesis theme, you can edit the post authors by filtering genesis_post_info

    The Code

    add_filter( 'genesis_post_info', 'EXAMPLE_genesis_post_info' );
    function EXAMPLE_genesis_post_info( $post_info = '' ) {
    	if ( is_singular( array ( 'videos', 'page' ) ) )
    		$post_info = 'By the Barbarians [post_edit]';
    	return $post_info;
    }
    

    Now all my posts are by Barbarians!

  • Light Fingered Fish

    Light Fingered Fish

    To explain the joke before we get too far, Jetpack’s contact form was originally called Grunion. The book “Memory” by Lois McMaster Bujold uses the phrase “light fingered fish” to talk about fish who elude hooks.

    I was building a site for my father and he wanted the contact form to redirect to another page. Thankfully you can do this with a filter on grunion_contact_form_redirect_url (see? Grunion? Fish?)

    The Code

    If you use the official code, then you’re going to need to know two things:

    1) What is the page ID you’re redirecting from
    2) What is the page slug you’re redirecting to

    Yes, it’s weird that you have to know those, but … well. That’s what we’ve got. I tried to come up with a reason why, and I think it’s just that searching for posts by slug is hard.

    function EXAMPLE_grunion_custom_form_redirect_url( $redirect, $id, $post_id ){
    
        $redirected_urls = array(
            '123' => home_url( 'contact' ),
            '456' => home_url( 'about' ),
            '789' => 'https://wordpress.org/surprise/',
        );
     
        foreach ( $redirected_urls as $source => $destination ) {
            if ( $id == $source ) {
                return $destination;
            }
        }
     
        // If there's no custom redirect, return the default
        return $redirect;
    }
    add_filter( 'grunion_contact_form_redirect_url', 'EXAMPLE_grunion_custom_form_redirect_url', 10, 3 );
    

    Buuuuut what if you wanted to do it by slug?

        $redirected_urls = array(
            'contact'  => home_url( 'contact-success' ),
            'about'    => home_url( 'about-success' ),
            'surprise' => 'https://wordpress.org/surprise/',
        );
     
        $slug = get_post_field( 'post_name', $id );
    
        foreach ( $redirected_urls as $source => $destination ) {
            if ( $slug == $source ) {
                return $destination;
            }
        }
    

    The benefit to this is you can change the post ID and, as long as it has the same slug, you’re good to go. Also let’s say you have a bunch of separate contact pages (contact-me, contact-mom and so on). You could use the logic to redirect all pages that have the word ‘contact’ or ‘about’ or ‘surprise’ …

        foreach ( $redirected_urls as $source => $destination ) {
            if ( strpos( $source, $slug ) !== false
                return $destination;
            }
        }
    
  • Types of Related Posts

    Types of Related Posts

    At it’s heart, related posts are the drive to help people find more content on your site. They serve no other purpose than keeping people on your site by piquing their interest in your words.

    But what if I told you there were multiple types of related posts for WordPress?

    Categories and Tags

    The first type of related posts are really just organization. I have three main categories on this site: How It Is, How It Works, and How To. I also have a million tags, like everyone else. If you wanted to read my thoughts on how things are, or rather why they are, you’d scroll through the category of “How It Works.” If you wanted to see everything I wrote about SVGs, you would check out my tag of ‘svg’ or possibly ‘images.’

    The point here is that categorization is a type of related posts. It’s entirely manual, but it’s the best way to say ‘These posts are like each other.’ And they have a fatal flaw. You see, if I wanted to read all the “How To” posts about SVGs, WordPress doesn’t easily cross relate. That is, I can’t list all the items in a category and a tag.

    Which is why we have …

    Related Posts Plugins

    There are two main types of plugins for this. There are the services, like Jetpack’s related posts, that scrape all your posts, toss them into a database, and use some complex algorithms to sort out what is and isn’t related. The other sort scan your posts locally and figure the same thing out.

    So which is better? Well. Jetpack requires you to trust Jetpack, or whatever service you pick, with your data. For some people, this can be a deal breaker. On the other hand, if you run it locally, you’re at the behest of how fast your site runs. For example, if it scans your posts live and you have, say, 300k posts, then that could be really slow. Or if it makes it’s own database table, how often is it going to update and cross relate?

    By the way, the 300k posts is not an exaggeration. That’s a site I looked at recently.

    Alertnative Relations

    There’s a secret third option, actually.

    I called it Semi Related Posts, and while I did it across post types, you could use the general logic. The concept is that instead of letting your site try and divine relations, you could manually connect them. It does require more upfront work, but cross relating posts by hand gives you the ultimate control.

    Of course, you’ll note that I did automate this as much as I could. I’m not crazy you know. If you can find a way to do that, maybe code a way to list 4 other posts in the same category and tags as this post, then you’ve automagically automated the simple.

    Until you hit that 300k post limit. Then you’ll have to rethink things again.