Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

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

  • Custom AMP Design

    Custom AMP Design

    The basic design of a page using AMP works for news articles. This is by design. It’s meant to be fast to load and fast to display, and that means removing the majority of cruft we shove in sidebars and footers. It means a streamlined website.

    A basic AMP page

    That’s a basic page but it works well for what it needs to be. The issue happens when you consider pages that aren’t your typical ‘news’ pages.

    Compare the information in a character page. On the left is the normal page and the right is the AMP page.

    An example character page with the regular on the left and the AMP on the right

    There’s some work to be done, obviously. Enter Custom Templates.

    Calling the Right Template for the Job

    The example code works great when you want to universally replace things. I don’t. I want to use a different template per-CPT, so my code is this:

    /*
     * AMP custom templates
     */
    add_filter( 'amp_post_template_file', 'lez_amp_post_template_file', 10, 3 );
    function lez_amp_post_template_file( $file, $type, $post ) {
    
    	$post_type=$post->post_type;
    
    	if ( 'post_type_characters' === $post_type || 'post_type_shows' === $post_type ) {
    		$file = get_stylesheet_directory() . '/amp-templates/shows-chars.php';
    	}
    	return $file;
    }
    

    That was the easy part.

    Making Your Template

    Now I need to make the template. It’s actually not as obvious as it might be. You can’t just copy the single.php file from the AMP templates folder and go. No, if you want to use it as-is, you will need to set up how to pull the custom templates.

    For the quick example, we have the Single Template and with that we have to replace all the calls to load_parts. They look like this:

    <?php $this->load_parts( array( 'header-bar' ) ); ?>
    

    We have to change them because the code for load_parts is relative to the file, and it doesn’t call .php so it’s a bit of a drama to call them. I replaced it with this:

    <?php include_once( WP_PLUGIN_DIR . '/amp/templates/header-bar.php' ); ?>
    

    This is pretty much what I tell plugin developers not to do. Never ever use WP_PLUGIN_DIR unless you absolutely have to. Always use plugins_url() and plugin_dir_path() please and thank you. But since I can’t use a URL in include_once() because it’s https) and I can’t use plugin_dir_path() because it’s relative to the file it’s called from, and this is called from the theme. So yes, here I have to use the one constant I tell you not to use. I’m aware of the irony.

    The rest of the template is mostly removing things I can’t filter out. I removed the feature image, I removed the byline (we don’t care who wrote the CPTs, authorship isn’t an issue), and I removed the taxonomies.

    Fix The Images

    Once the main file is loading properly, it’s time to address the two problems with the images: size and location. There’s, thankfully, some default code that can help with this that didn’t need a template. AMP comes with directions on changing your featured image but the example code had to be tweaked in order to handle multiple post types:

    add_action( 'pre_amp_render_post', 'lez_amp_add_custom_actions' );
    function lez_amp_add_custom_actions() {
    	add_filter( 'the_content', 'lez_amp_add_featured_image' );
    }
    
    function lez_amp_add_featured_image( $content ) {
    	global $post;
    
    	$post_type=$post->post_type;
    	$post_id=$post->ID;
    	$image_size = 'featured-image';
    
    	if ( $post_type === 'post_type_characters' ) $image_size = 'character-img';
    	if ( $post_type === 'post_type_shows' ) $image_size = 'character-img';
    
    	if ( has_post_thumbnail() ) {
    		$image = sprintf( '<p class="lezwatch-featured-image">%s</p>', get_the_post_thumbnail( $post_id, $image_size ) );
    		$content = $image . $content;
    	}
    	return $content;
    }
    

    But. This made double images! Why? Because this is a lie: “The default template does not display the featured image currently.”

    It does. This is why I removed the featured image in my template. If I can figure out how not to do that, I’ll be 90% of my way to not needing a custom template at all, because everything else I did in the filter.

    Add the Extra Content

    Remember function lez_amp_add_featured_image() above? Yeah I threw it out and made an insanely complex. I renamed it function lez_amp_add_content() and the if ( $post_type === 'post_type_characters' ) {} section became huge so that I could output the code the way I wanted.

    Filtered content for the AMP pages for characters

    In the image above, you can see I’ve not only added in a lot more meta content from the post, but I also tweaked the CSS to float the image to the left and resize the SVGs. I went super image light on this, loading the smallest image that would work, and the SVGs are very small sized.

    Overall? It should be easier

    The biggest issue I have with this is that I wish I could filter more. If I could turn off the featured images and remove the bylines, then I wouldn’t need the custom template at all. At least, not for the characters. The shows? That’s another matter.