Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

  • Recoding Taxonomies

    Recoding Taxonomies

    It’s hard to know what the future will be. When initially building out a site, it’s a game of guesswork to name things. You want to name them to be long term sustainable, but without a crystal ball, there will be missteps.

    Take, for example, the case of custom taxonomies. When my site was originally built out, it only had one custom set of tags and they were logically called sitename_tags. As the site grew, it became apparent that multiple tags were going to be needed and, in the end, it had five.

    What Are Our Names

    While it’s relatively easy to rename a taxonomy’s display name by editing the data in register_taxonomy, you don’t want to rename the rewrite after the fact. You can do it, of course, but I recommend you be really up to speed on your redirects before you try. Obviously the best choice is to pick good names in the beginning. But what names are we actually talking about?

    1. The Display Name – This is the name your readers (and you) see. Categories and Tags are display names. In my case, Stations, Clichés, and Tropes are display names. You can change these as you need.
    2. The Slug – This is the rewrite I mentioned. It’s in your URL. Don’t change this unless you know redirects.
    3. The Taxonomy Name – This is secret. You’d only know what it was if you hovered over the links and noticed that tags have a taxonomy of post_tag.

    Today we’re talking about that third one.

    Why Bother?

    If you’re the only one who really sees that you named your stations show_tag instead of show_station, why does it matter?

    As it turns out, if you’re working on the theme and plugins for a site, you will end up running into those quirks when you write a loop to perform complex displays on your data. It’s both annoying to have to always comment things like “Yes show_tag means show_station” so that future you knows what the hell you’re doing.

    Like my friend says, the best documentation for a function is a good name, and fixing the name will make you feel better and smarter.

    How To Do It

    This requires two steps:

    1) Run a search/replace on your files to swap the names out
    2) Run a search/replace on your database to swap the names out

    The reason is that those two things have to be in sync. Since my world lives in a git repo and a server with WP-CLI, I ran a search/replace on my files, pushed the changes, and then ran a quick wp search-replace show_tag show_station to clean it up.

    The important thing to note is that what you’re changing in your database is not post content, but meta information and tags tables.

  • Copyright Years for WordPress

    Copyright Years for WordPress

    At the bottom of every page on my site is a little bit of info declaring copyright: “Copyright © 2017 Mika A. Epstein”

    How do I do that and not have to update all my site themes and widgets every year? With code, of course! I have both a function I could use in themes and a shortcode I could use anywhere a shortcode can be used.

    The Code

    There are two functions, the base code and the shortcode. The concept is that if you don’t put in a year (which is the start year for your copyright) it will only show the current year. If you do put in year, it forces it to be an integer and then does a couple checks. The checks were originally as follows:

    1. Is $year ‘auto’? Force this year.
    2. Is $year this year? Force this year.
    3. Is $year equal to 0? Force this year.
    4. Is $year greater than this year? Oh, silly human. Force this year.
    5. Is $year less than this year? Use the ‘start – end’ format

    The reason for this is practical. We’re sanitizing things as early as we can, and then we’re checking for the logical and illogical entries. If someone decides the year is ‘Bob’ then intval() throws a 0 and since 0 isn’t actually a valid year in the Gregorian calendar, then I can do a simple “if 0” check.

    But … I’m not so bold as to assume the only people who will want this are using the Gregorian calendar. To be more universal, I changed the code to make the first check for if the year was set to ‘auto’ (which defaults to this year), or if it was a non-number. If it’s not a number, you get forced this year. Otherwise, the code trusts you.

    function helf_auto_copyright( $year = 'auto' , $text = '' ){ 
    	$year = ( $year == 'auto' || ctype_digit($year) == false )? date('Y') : intval($year);
    	$text = ( $text == '' )? '©' : sanitize_text_field( $text );
    
    	if( $year == date('Y') || $year > date('Y') ) $output = date('Y');
    	elseif( $year < date('Y') )  $output = $year . ' - ' . date('Y');	
    
    	echo $text . ' ' . $output;
    }
    
    function helf_auto_copyright_shortcode( $atts ) {
        $attributes = shortcode_atts( array(
            'year' => 'auto',
            'text' => '&copy;'
        ), $atts );
    
        return helf_auto_copyright( sanitize_text_field($attributes['year']), sanitize_text_field($attributes['text']) );
    }
    add_shortcode( 'copyright', 'helf_auto_copyright_shortcode' );
    

    The one failing here is I only account for the common era (or ‘AD’ for those who didn’t know we all switched to CE a while back). I’m sure this can be extended to BCE if so desired. Spitballing, I’d just use negative numbers, check for them and output ‘year BCE – year CE’ instead. But that’s a little much for this use case.

    Usage

    As a shortcode: Copyright 2016 - 2025

    As a function: helf_auto_copyright_shortcode( '2016', 'Copyright' );

    Both will output the same thing (as of 2017): Copyright 2016 – 2017

    And in 2018? It will magically update for you.

  • Displaying Taxonomy Count

    Displaying Taxonomy Count

    Monday we displayed post counts. Well, what about taxonomies? That’s a little more complicated, I’m afraid.

    Posts are easy. You pick a post type, you display the number of published posts, you walk away. Taxonomies though are a mixed bag. By default you have categories (category) and tags (post_tag) and inside them, you have terms. For example ‘Uncategorized’ is a category. The problem is that to check if a taxonomy exists (and display a post count), you have two have both the taxonomy name and the term name.

    While you think you could just write a loop ‘If the term name isn’t in categories, it’s in tags!’ the reality is that anyone can add any taxonomy and, worse, term names aren’t unique. It’s ironic here, because I desperately wanted term names and slugs to not be unique. I wanted to have a tag for ‘random’ and a category for ‘random’ and they all have the same slug names. So here I am now, realizing I’ve set myself up for disaster.

    The options are simple:

    1. Force people to use term and taxonomy
    2. Somehow be clever

    I went with option 2. Allow people to use term and taxonomy, but if they don’t, find the first instance and go for it.

    The Code

    // [numtax term="term_slug" taxonomy="tax_slug"]
    function numtax_shortcode( $atts ) {
    	$attr = shortcode_atts( array(
    		'term'     => '',
    		'taxonomy' => '',
    	), $atts );
    
    	// Early Bailout
    	if ( is_null($attr['term']) ) return "n/a";
    
    	$the_term = sanitize_text_field( $attr['term'] );
    	$all_taxonomies = ( empty( $attr['taxonomy'] ) )? get_taxonomies() : array( sanitize_text_field( $attr['taxonomy'] ) );
    
    	//$all_taxonomies = get_taxonomies();
    	foreach ( $all_taxonomies as $taxonomy ) {
    	    $does_term_exist = term_exists( $the_term, $taxonomy );
    
    	    if ( $does_term_exist !== 0 && $does_term_exist !== null ) {
    		    $the_taxonomy = $taxonomy;
    		    break;
    	    } else {
    		    $the_taxonomy = false;
    	    }
    	}
    
    	// If no taxonomy, bail
    	if ( $the_taxonomy == false ) return "n/a";
    
    	$to_count = get_term_by( 'slug', $the_term, $the_taxonomy );
    
    	return $to_count->count;
    
    }
    add_shortcode( 'numtax', 'numtax_shortcode' );
    

    There are two moments where I bail out early. If they forgot to put in a term, display “N/A”. The same if we get all the way to the end and there was no found taxonomy.

    Also if someone puts in a taxonomy, I treat it as an array in order to be lazy and not repeat myself. Good coding doesn’t repeat, so since I have to loop the array of found taxonomies in order to find the matchup, I may as well use it once to find the same data when I know what I have.

    I admit, I was really excited here since I finally got to use ternary operations. I’ve known how they work for ages, but I never had a moment where it was so obvious to use them.

  • Displaying Post Count

    Displaying Post Count

    If you need a count of all your posts in WordPress, there’s a pretty handy function called wp_count_posts() for that.

    For example, if you have a post-type of ‘characters’ and you wanted to show a count of that, you can do this:

    $to_count = wp_count_posts( 'characters' );
    printf( __('Total Characters: %s'), $count->publish );
    

    And that’s all well and good, but I was working on some SEO thoughts. In doing so, I ran into the recommendation that, for a ‘company’ type page, having a list of your posts was a bad idea. This made sense. After all, a site that is about non-traditional blog content shouldn’t start with a blog.

    To correct this, I restructured the front page to show, in order:

    1. An introduction to the site
    2. The 4 newest characters added
    3. The 4 newest shows added
    4. The latest 10 blog posts

    This keeps the blog information visible (which helps show that content is being updated) but also doesn’t drop people into a cold open. They can understand why they’re on the site, what they’re getting, and where to go.

    But part of telling people about the site meant I wanted to indicate the depth of information. When I used to run a MediaWiki site, I used {{NUMBEROFARTICLES}} to list the number of articles. It was a very obvious way to display activity and attentiveness. If a site had 500 articles, it was probably getting up there. If it had 1500, it probably knew what it was doing.

    That meant with WordPress what I wanted was a dynamic way to show the number of posts in a post type, like [numberofposts type="characters"] with the default being posts.

    The Code

    function numberofposts_shortcode( $atts ) {
    	$attr = shortcode_atts( array(
    		'type' => 'post',
    	), $atts );
    
    	$posttype = sanitize_text_field( $attr['type'] );
    	if ( post_type_exists( $posttype ) !== true ) $posttype = 'post';
    
    	$to_count = wp_count_posts( $posttype );
    
    	return $to_count->publish;
    
    }
    add_shortcode( 'numberofposts', 'numberofposts_shortcode' );
    

    What I chose to do here was check if the post type exists and, if not, force it to show posts. That way there will never be an error, though it may not show you what you expected if you put in ‘posts’ as your post type.

  • Prettier Search Queries

    Prettier Search Queries

    By default, when you search on a WordPress site, your search URL has an /?s= parameter. Back in the old days of WordPress, we all had URLs like /?p=123 where 123 was the page ID. With the advent of Pretty Permalinks, we moved to pretty URLs like /2016/prettier-search-queries/ and everyone was happier.

    What about search?

    As it happens, the WP Rewrite API actually has a search base of … search. If you go to your Settings > Permalinks page, you won’t see it there, and yet on every site if you go to https://halfelf.org/search/apache you’ll actually get that nice, pretty path.

    Because of that, you could get away with adding this to your .htaccess file in order to get those pretty URLs.

    RewriteCond %{QUERY_STRING} s=(.*)
    RewriteRule ^$ /search/%1? [R,L]
    

    You can also use a plugin like Mark Jaquith’s Nice Search.

    Those methods work for nearly all sites.

    But you know me. I’m not ‘all’ sites.

    Extra Paramater Headache

    I had a different problem. Because my site had specialized data, it had extra search parameters. I was intentionally limiting my search to specific post type. This meant my URLs looked like this: /?s=rookie+blue&post_type[]=post_type_shows

    When I translated that to use the pretty search, well …/search/rookie+blue&post_type[]=post_type_shows just didn’t work.

    This is for a pretty obvious reason when you study the URLs. The first one has ?s=... and then later an &, while the second only has the & in there. If I changed the URL to this, it worked: /search/rookie+blue/?post_type[]=post_type_shows

    The reason for this was due to how parameters work in URLs. They have to start with ? at the beginning. All additional parameters are added with ?param=value after that.

    Semi Pretty Search Permalinks

    To me, the nicest URLs would be `/search/rookie+blue/section/shows/’. The reality is that people will search shows and characters and I wasn’t quite sure how I wanted to handle that. Did I want them to be sections separated by plus signs, or extra ‘folders’ or what? In the end, I decided that for now it was okay to just make these prettier.

    Taking Mark’s code as my start point, I came up with this:

    function pretty_permalink_search_redirect() {
    	// grab rewrite globals (tag prefixes, etc)
    	// https://codex.wordpress.org/Class_Reference/WP_Rewrite
    	global $wp_rewrite;
    
    	// if we can't get rewrites or permalinks, we're probably not using pretty permalinks
    	if ( !isset( $wp_rewrite ) || !is_object( $wp_rewrite ) || !$wp_rewrite->using_permalinks() )
    		return;
    
    	// Set Search Base - default is 'search'
    	$search_base = $wp_rewrite->search_base;
    
    	if ( is_search() && !is_admin() && strpos( $_SERVER['REQUEST_URI'], "/{$search_base}/" ) === false ) {
    
    		// Get Post Types
    		$query_post_types = get_query_var('post_type');
    		if ( is_null($query_post_types) || empty($query_post_types) || !array($query_post_types) ) {
    			$query_post_types = array( 'post_type_characters', 'post_type_shows' );
    		}
    
    		$query_post_type_url = '/?';
    		foreach ( $query_post_types as $value ) {
    			$query_post_type_url .= '&post_type[]=' . $value ;
    		}
    
    		wp_redirect(
    			home_url( "/{$search_base}/"
    			. urlencode( get_query_var( 's' ) )
    			. urldecode( $query_post_type_url )
    			) );
    		exit();
    	}
    }
    add_action( 'template_redirect', 'pretty_permalink_search_redirect' );
    

    And that actually does work exactly as I want it to.

  • Damn You, Autocorrect!

    Damn You, Autocorrect!

    After the seventh time I shouted “stop autocorrecting cmb2 fields!” at my site, I knew I had to do something.

    When you run a website where you enter a lot of people’s names, Autocorrect is a curse more than a blessing. Of course I want it on my post content, but when I get to the field where I enter someone’s name, for crying out loud, some names like Nuñez just don’t meet a spell check. And don’t get me started on my friend’s names or my own. I’ve lost track of the number of times I ended up as “Mike.”

    This issue used to only be on phones and tablets. Then Apple introduced autocorrect to their MacOS, which resulted in a lot of tweets followed up by “Damn you, Autocorrect!”

    HTML Attributes

    If you’ve got a form and you want to tell autocorrect to go away, the code looks like this:

    <input autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
    

    For the most part, this will work. You don’t need all of them all of the time, but in my case I was adding names and “Debbie van Houten” was one problem and “Dr. el Farad” was another. I wanted it to just shut up and let me type the name as their parents intended, no matter what. I went whole hog.

    But as I mentioned, I use CMB2 and I needed to stop my site from autocorrecting CMB2 fields. It was time for some code.

    CMB2 Custom HTML Attributes

    This one is so straightforward I was delighted. When you create a new field, you can set arbitrary attributes.

    // Field: Actor Name
    $cmb_characters->add_field( array(
    	'name'				=> 'Actor Name',
    	'desc'				=> 'Include years (in parens) for multiple actors',
    	'id'				=> $prefix . 'actor',
    	'type'				=> 'text',
    	'repeatable'		=> 'true',
    	'attributes'		=> array(
    		'autocomplete'		=> 'off',
    		'autocorrect'		=> 'off',
    		'autocapitalize'	=> 'off',
    		'spellcheck'		=> 'false',
    	),
    ) );
    

    That was all I needed to do in order to get autocorrect to duck itself. Now I was free to write however weird a name I needed without worrying that autocorrect wanted to call me Mike. Again.

    Thanks, autocorrect. Thanks a lot.