Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Rebranding Featured Image

    Rebranding Featured Image

    WordPress 3.0 introduced featured images, letting everyone designate an image for a post (or page) in a way that themes have taken great advantage of in the intervening years. The concept of a ‘featured’ image was easy enough to understand. An image the content featured. Cool. But it can do so much more.

    The Defaults

    The basic box is one all WordPress users are familiar with:

    The basic featured image box

    A simple box that says “Featured Image” with a link to set the featured image.

    It’s great, but it’s a little boring.

    Changing the Title

    The actual trick to this is to remove the metabox. The three attributes you will need are the name of the meta box, the post type, and the location. The post type is the one most of you are going to change. For example, I wanted to change the title for a specific post type so it was clearer what kind of image I was looking for to use as a featured. For TV shows, I wanted it to indicate that I was looking for a Title Card type image.

    add_action('do_meta_boxes', 'featured_image_title_post_type_shows');
    function featured_image_title_post_type_shows() {
        remove_meta_box( 'postimagediv', 'post_type_shows', 'side' );
        add_meta_box('postimagediv', __('Show Image/Title Card'), 'post_thumbnail_meta_box', 'post_type_shows', 'side');
    }
    

    This little snippet removes the meta box and then adds it back with a new title:

    Customized feature image with new title

    Changing the Main Content

    Once the title is changed, the next step is to change the content. I’ve done this before, putting in the image size to the content. What I wanted to do here was change the content from “Set featured image” to “Set show image” and for that, I want to use a string replacement:

    add_filter( 'admin_post_thumbnail_html', 'set_featured_image_text_post_type_shows' );
    function set_featured_image_text_post_type_shows( $content ) {
        global $current_screen;
    
        if( !is_null($current_screen) && 'post_type_shows' == $current_screen->post_type )
            return $content = str_replace( __( 'Set featured image' ), __( 'Set show image' ), $content);
        else
            return $content;
    }
    

    But that only changed the one. Once you’ve set a featured image, the language changes to “Remove featured image”. I could add in more replacements, or I could be smart. We like being smart.

    return $content = str_replace( __( 'featured' ), __( 'show' ), $content);
    

    And yes, it works.

    Customizing by Type

    What if you have ten custom post types? Well then it’s time to get smart! By using anonymous functions, we can do this quite effectively, getting a list of all the publicly registered post types that aren’t the built-ins, and then for each one, renaming the content:

    // Customize Featyred images for CPTs
    add_action( 'admin_init', 'helf_featured_images' );
    function helf_featured_images() {
    	$post_type_args = array(
    	   'public'   => true,
    	   '_builtin' => false
    	);
    	$post_types = get_post_types( $post_type_args, 'objects' );
    	foreach ( $post_types as $post_type ) {
    
    		$type = $post_type->name;
    		$name = $post_type->labels->singular_name;
    
    		// change the default "Featured Image" metabox title
    		add_action('do_meta_boxes', function() use ( $type, $name ) {
    			remove_meta_box( 'postimagediv', $type, 'side' );
    			add_meta_box('postimagediv', $name.' Image', 'post_thumbnail_meta_box', $type, 'side');
    		});
    
    		// change the default "Set Featured Image" text
    		add_filter( 'admin_post_thumbnail_html', function( $content ) use ( $type, $name ) {
    			global $current_screen;
    			if( !is_null($current_screen) && $type == $current_screen->post_type ) {
    				return $content = str_replace( __( 'featured' ), strtolower( $name ) , $content);
    			} else {
    				return $content;
    			}
    		});
    	}
    }
    

    This trick uses the values of a custom post type which you already set when you built out your custom post types.

    Customizing the Content Further

    Now that we’ve changed ‘featured’ to match our custom post types, it’s time to take it even further. Can we smartly add in the image size? Usually the answer here is no, because it’s impossible to know what you want to have as your featured image size for each custom post type. As it happens I can because I was smart.

    Every single CPT that has a special image has an image size for it:

    add_image_size( 'character-img', 150, 200, true );
    add_image_size( 'show-img', 960, 400, true );
    

    And my post-names for those things are ‘post_type_characters’ and ‘post_type_shows’. This means I can add the following to my filter for admin_post_thumbnail_html:

    		add_filter( 'admin_post_thumbnail_html', function( $content ) use ( $type, $name ) {
    			global $current_screen;
    			if( !is_null($current_screen) && $type == $current_screen->post_type ) {
    			    // Get featured image size
    			    global $_wp_additional_image_sizes;
    			    $genesis_image_size = rtrim( str_replace( 'post_type_', '', $type ), 's' ).'-img';
    
    			    if ( isset( $_wp_additional_image_sizes[ $genesis_image_size ] ) ) {
    			        $content = '<p>Image Size: ' . $_wp_additional_image_sizes[$genesis_image_size]['width'] . 'x' . $_wp_additional_image_sizes[$genesis_image_size]['height'] . 'px</p>' . $content;
    			    }
    				$content = str_replace( __( 'featured' ), strtolower( $name ) , $content);
    			}
    			return $content;
    		});
    

    This could even be extended to only show the image size when it was not set. For now, this does what I want.

    How’s it look?

    Not bad:

    Featured Images renamed and rebranded

    And there you have it. More information for your users to know what kind of and size image to use.

  • 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 == '' )? '&copy;' : 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.