Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: custom post types

  • Flushing Another Post’s Varnish

    Flushing Another Post’s Varnish

    The other day I talked about updating another post’s meta. That’s all well and good, but what happens when you’ve cached that other page?

    Most cache tools work via the simplistic understanding that if you edit a page, you want to empty the cache for that page. The problem with my clever post-meta updating is that doesn’t actually update the post. Oh, sure it updates the data, but that doesn’t perform an update in a way that most plugins understand.

    Thankfully this can be done programmatically in a few different ways, depending on what plugin is used.

    1. Tell Varnish to empty the page’s cache any time post meta is updated
    2. Add a force-empty command to the function that updates the meta

    Using Varnish HTTP Purge

    Since I’m running on DreamPress, my plugin of choice is Varnish HTTP Purge. This is a very simple plugin that has no real interface by design. Mike Schroder and I picked it for use because of it’s beautiful simplicity, and when it was abandoned, adopted it. I’m rather well acquainted with it and at the behest of others, I wrote in code where people could add extra URLs to flush as well as extra events to trigger. There’s even a wiki doc about it.

    However. That’s not what I did here because I wanted to limit when it runs as much as possible.

    When To Empty Cache

    There’s a part of caching that is always difficult to manage. When should a page be emptied and when should a whole section of pages be emptied? The obvious answer is that we should retain a cache as long as possible, emptying select pages only when necessary.

    With WordPress posts and pages it’s pretty easy to know “Update the post, empty the cache.” But when you start talking about things like the JSON API, it’s a lot harder. Most plugins handle the JSON API for you, but if you’ve built your own API (like say /wp-json/lwtv/) and built out a lot of custom APIs (like say stats or an Alexa skill) you will want to flush that whole darn thing every single time.

    The Code

    Okay so how do we do that easily?

    In the function I showed you the other day, I added this right before the re-hook at the end:

    if ( class_exists( 'VarnishPurger' ) ) {
    	$varnish_purge = new VarnishPurger();
    	$varnish_purge->purgeUrl( get_permalink( $post_id ) );
    }
    

    That makes sure the Varnish plugin is running and calls a URL to flush. Done. If you want to have it do more URLs, you can do this:

    if ( class_exists( 'VarnishPurger' ) ) {
    			
    	$purgeurls = array( 
    		get_permalink( $post_id ) . '?vhp-regex',
    		get_site_url() . '/page1/',
    		get_site_url() . '/custom_page/',
    		get_site_url() . '/wp-json/lwtv/?vhp-regex',
    	);
    			
    	foreach ( $purgeurls as $url ) {
    		$this->varnish_purge->purgeUrl( $url ) ;
    	}
    }
    

    And then you can add in as many as you want.

  • Updating Another Post’s Meta

    Updating Another Post’s Meta

    Over on LezWatch TV we have two post types, shows and characters. In an added wrinkle, there’s data that exists on the show pages that is updated by the character pages.

    That means when I save a character page, I need it to trigger a series of updates to post meta of whatever shows the character belongs to. But because of the complexities of the data being saved, I need to run it when the show saves too.

    The Code

    add_action( 'do_update_show_meta', 'lezwatch_update_show_meta', 10, 2 );
    add_action( 'save_post_post_type_shows', 'lezwatch_update_show_meta', 10, 3 );
    add_action( 'save_post_post_type_characters', 'lezwatch_update_show_meta_from_chars', 10, 3 );
    
    function lezwatch_update_show_meta( $post_id ) {
    
    	// unhook this function so it doesn't loop infinitely
    	remove_action( 'save_post_post_type_shows', 'lezwatch_update_show_meta' );
    
    	[Update all the post meta as needed here]
    
    	// re-hook this function
    	add_action( 'save_post_post_type_shows', array( $this, 'lezwatch_update_show_meta' ) );
    
    }
    
    function lezwatch_update_show_meta_from_chars( $post_id ) {
    	$character_show_IDs = get_post_meta( $post_id, 'lezchars_show_group', true );
    	if ( $character_show_IDs !== '' ) {
    		foreach ( $character_show_IDs as $each_show ) {
    			do_action( 'do_update_show_meta' , $each_show['show'] );
    		}
    	}
    }
    

    This runs three actions, saving shows and characters, and also a custom one that the character page will call. It needed to be split like that because some characters have multiple shows.

  • Demi Related Post Types

    Demi Related Post Types

    In my last post, I talked about relating posts to a custom post type. That is, how I listed the blog posts tagged with a show on the show page. Let’s do the reverse!

    Continuing the Click Hole

    While we’re very good about remembering to tag the posts we write with the shows they’re about, we’re not always as good with remembering to link the first instance of the show name to the show. That is, when we write about Supergirl we sometimes forget to link that to the page for her.

    That said, we still want to make it ‘easy’ for people to figure out where they can read more about the show. The ‘simplest’ way to do that would be to have a block at the bottom of each blog post. “Want to know more about the shows in this post?” or something along those lines.

    Relations Back Across Post Types

    The difficulty here is that if you want to get post data based on a post name, it’s easy. There’s a handy little function get_page_by_title() where you can put in this: get_page_by_title( 'Supergirl', OBJECT, 'post_type_shows' ); where Supergirl is the name of the show and post_type_shows is the post type.

    That’s well and good, but I knew there would be cases where the tag wasn’t going to match the name. People name shows pretty weird stuff (#hastag for example is the name of a show). Instead, I knew that I needed the tag slug and for that, I needed a different function: get_page_by_path()

    The Code

    Here’s the magic sauce:

    function related_shows( $content ) {
        if ( is_singular( 'post' ) ) {
    		$posttags = get_the_tags( get_the_ID() );
    		$shows = '';
    		if ( $posttags ) {
    			foreach ( $posttags as $tag ) {
    				if ( $post = get_page_by_path( $tag->name, OBJECT, 'post_type_shows' ) ) {
    					$shows .= '<li><a href="/show/' . $post->name . '">'. $tag->name . '</a></li>';
    				}
    			}
    		}
    		if ( $shows !== '' ) {
    			$content .= '<section class="related-shows"><div><h4 class="related-shows-title">Read more about shows mentioned in this post:</h4><ul>' . $shows . '</ul></div></section>';
    		}
    	}
    	return $content;
    }
    

    This has a few failsafes in there, like it only runs on single posts, it has to have at least one tag, and it has to have at least one tag that’s a show. Whew.

    After that, it’s just the CSS.

  • Plural URLs

    Plural URLs

    URLs can be hard. When you have custom post types in WordPress it can be harder.

    Take, for example, a custom post type for videos. Do you want your URLs to be http://example.com/videos/video-name/ or http://example.com/video/video-name/ ? And do you want the archive to be http://example.com/videos/ or http://example.com/video/ ? And what happens when you change your mind?

    Thankfully, WordPress lets you do some weird things.

    Pick Your Default

    Let’s look at the video/videos idea for a moment. Individual posts should be video but the archive should be videos in order to grammatically make sense. When you make your custom post type there’s a parameter called has_archive – by default it’s false. If you make it true, then it’ll have the same ‘base’ as your custom post types.

    But. If you make it a string then you can make it ‘videos’ or ‘photos’ and magically your archives will have those names. That makes it pretty easy to change, just remember to re-save your permalinks after. I personally recommend doing a redirect so that video goes to videos (and videos/postname go to video/postname) so that any random bad URLs would still be caught.

    Remember that you can leave it false and make a page to be a placeholder page, or you use archive-{post_type}.php to customize it further.

    When You Need Both

    But… What if you need both?

    This is probably a bad idea, but let’s pretend you want to have both video and videos work for all cases. That’s when you’ll need something like this:

    $plural_types = array( 
    	'videos' => 'post_type_videos', 
    	'photos' => 'post_type_photos' 
    );
    
    foreach( $plural_types as $slug => $type ) {
    	add_rewrite_rule(
    		'^'.$slug.'/?$',
    		'index.php?post_type='.$type,
    		'top'
    	);
    	add_rewrite_rule(
    		'^'.$slug.'/page/([0-9]+)?/?$',
    		'index.php?post_type='.$type.'&paged=$matches[1]',
    		'top'
    	);
    }
    

    In that example, I have the slug for my custom post types set to the singular, and then the $plural_types array has the correct plural and the associated custom post type. This is tossed into a for-loop that creates a custom rewrite rule that will redirect.