WordPress: Hacking Navigation to stay in category

Out of the box, WordPress post navigation goes to the last post that you’ve written. This makes sense for a basic blog, but I, like many, use categories and other techniques to separate my blog into different threads of thought. If you’re looking at technical stuff and then back into something “off topic” it’s confusing at best, off-putting at worst. Maybe you just want to figure out how to make something work and aren’t really interested in my thoughts about sports or politics.

This seems to come up often enough that some sort of navigation tweaking would already be built into WordPress. It is, sort of, but it requires going into the code a little. There are plenty of discussions telling you what to tweak, but I wanted to write this to explain a little of my own journey for this, where I got confused, and how I worked it out. I think it may be helpful to WordPress  and PHP newbies who feel thrown to the wolves when they need to hack something. Ultimately, when I was done, it was all pretty obvious. My goal is to save you a little time and frustration and help you to have a more solid and effective strategy in little tweaks that you need to do.

I will cover:

Initial research and confusion

Basically, I wanted to have a set of posts, grouped by category. I wanted navigation to only show you what was in that category. I knew I had done this before and it was a simple fix, but the template I was using, Twentyfourteen (for reasons… Don’t judge), handled the design a little differently.

The basic change is pretty straight forward. In the templates that display a post there are navigation functions that kick in under the content to show links for the next and previous posts. These functions are documented by WordPress on their site. Here’s the information about next_post_link(), with pointers to the related function previous_post_link(). I’ll admit, that looking at this documentation did not clarify it all for me. So, I did a little digging.

The most popular answer was this simple tweak, which I found repeated in a number of places, with varying degrees of snarky comments. It’s a pretty easy fix. Find the section for the navigation and add “TRUE” at the end of the function call. This will cause the navigation to stay within the same taxonomy, which defaults to category. (Later I may tinker with changing this taxonomy, but that wasn’t necessary right now.)

So, we change this:

previous_post_link('« %link', '%title');
next_post_link('%link »', '%title');

to this:

previous_post_link('« %link', '%title' TRUE);
next_post_link('%link »', '%title' TRUE);

The location of this navigation code typically lies in the template for showing the post. In the past, I edited a file called single.php. The problem was, that when I went into that file, I didn’t see any references to the post_link functions. I saw this:

<?php
/**
* The Template for displaying all single posts
*
* @package WordPress
* @subpackage Twenty_Fourteen
* @since Twenty Fourteen 1.0
*/

get_header(); ?>
<div id="primary" class="content-area">
<div id="content" class="site-content" role="main">
<?php
// Start the Loop.
while ( have_posts() ) :
the_post();
/*
* Include the post format-specific template for the content. If you want to
* use this in a child theme, then include a file called content-___.php
* (where ___ is the post format) and that will be used instead.
*/
get_template_part( 'content', get_post_format() );
// Previous/next post navigation.
twentyfourteen_post_nav();

// If comments are open or we have at least one comment, load up the comment template.
if ( comments_open() || get_comments_number() ) {
comments_template();
}
endwhile;
?>
</div><!-- #content -->
</div><!-- #primary -->
<?php
get_sidebar( 'content' );
get_sidebar();
get_footer();

I find a clearly marked navigation section, which points to twentyfourteen_post_nav(). A little digging turns this function up in another file under the twentyfourteen template structure called inc/template-tags.php.

function twentyfourteen_post_nav() {
// Don't print empty markup if there's nowhere to navigate.
$previous = ( is_attachment() ) ? get_post( get_post()->post_parent ) : get_adjacent_post( false, '', true );
$next = get_adjacent_post( false, '', false );
if ( ! $next && ! $previous ) {
return;
}
?>
<nav class="navigation post-navigation" role="navigation">
<h1 class="screen-reader-text"><?php _e( 'Post navigation', 'twentyfourteen' ); ?></h1>
<div class="nav-links">
<?php
if ( is_attachment() ) :
previous_post_link( '%link', __( '<span class="meta-nav">Published In</span>%title', 'twentyfourteen' ) );
else :
previous_post_link( '%link', __( '<span class="meta-nav">Previous Post</span>%title', 'twentyfourteen' ) );
next_post_link( '%link', __( '<span class="meta-nav">Next Post</span>%title', 'twentyfourteen' ) );
endif;
?>
</div><!-- .nav-links -->
</nav><!-- .navigation -->
<?php
}

And, here are my next_post_link() and previous_post_link() calls! Success! Or is it? I need to think about this a little.

Creating a child theme, and why I did it that way

I was going to make the edits to the file in the twentyfourteen template directly; but if I did that I would have to keep up with it. I would have to re-hack this every time that an update occurred.  A child theme is really the right approach. That way, as long as that code remains reasonably intact, my fix will still work. If it changes somewhere, then I have to revisit my solution, but likely it will be a permanent fix. I like that. So, I create a child theme.

The WordPress documentation on child themes is pretty clear. I do the following steps.

  • I create a twentyfourteen-child directory
  • I add a style.css and a functions.php file into this directory with the basic templates
  • I tweak style.css to include the previous theme and have the right names and information.
  • I add my hacked function into functions.php

The documentation tells you to create a new directory in the same directory where your theme resides. The convention is to put “-child” after the theme name, but you can actually call it anything that you want. In this case, I just followed the convention and called it twentyfourteen-child.

Inside this directory, I created two files. The first is the style.css, which contains the general logic to connect your child theme with the parent theme. The documentation included a template which looks like this:

/*
Theme Name: Twenty Fifteen Child Theme
URI: http://example.com/twenty-fifteen-child/
Description: Twenty Fifteen Child Theme
Author: John Doe
Author URI: http://example.com
Template: twentyfifteen
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Tags: light, dark, two-columns, right-sidebar, responsive-layout, accessibility-ready
Text Domain: twenty-fifteen-child
*/

This preamble is included at the top of the style.css file in the twentyfourteen directory, followed by a lot of style definitions. To begin, we really only need the preamble. If we make changes to styles, they will go here, and everything here will override the previous definitions. I’m only doing function code, so my style.css  looks like this (my edits are emphasized):

/*
Theme Name: Twenty Fourteen Child
Theme URI: http://mythmade.com/twenty-fourteen-child/
Description: Twenty Fourteen Child
Theme Author: Chris Walden
Author URI: http://mythmade.com
Template: twentyfourteen
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Tags: blog, news, two-columns, three-columns, left-sidebar, right-sidebar, custom-background, custom-header, custom-menu, editor-style, featured-images, flexible-header, footer-widgets, full-width-template, microformats, post-formats, rtl-language-support, sticky-post, theme-options, translation-ready, accessibility-ready
Text Domain: twentyfourteen-child
*/

That’s really it for the style.css. We’re just putting the right information in so that WordPress can find what it needs. Not doing this causes WordPress to not find your child theme. If you can’t see it in the themes listing to activate, double-check that you have included everything in this file. Now we create the functions.php. Again, the documentation has some standard templating here. 

<?php
function my_theme_enqueue_styles() {
$parent_style = 'parent-style'; // This points to the parent style

wp_enqueue_style( $parent_style, get_template_directory_uri() . '/style.css' );
wp_enqueue_style( 'child-style',
get_stylesheet_directory_uri() . '/style.css',
array( $parent_style ),
wp_get_theme()->get('Version')
);
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' );

?>

Really not much to do there. At this point, I should be able to activate the theme and it will be identical to the original twentyfourteen style because it’s just calling all the original code. So far so good!

Finding the right things to hack in the right ways

So, now that I have a child theme, I simply replace elements with something that does what I want. My replacement will override the original theme elements. If twentyfourteen ever receives an update, my changes should continue to work. I just need to decide what to hack. I have a few options.

  1. I change the single.php template. It currently does a call to  twentyfourteen_post_nav(), but I could replace that call with my own navigation routine.
  2. I copy the inc/template-tags.php and make my changes in there. I could delete all but the relevant part.
  3. I copy the  twentyfourteen_post_nav() function into my child functions.php and change it there.

I decide to go with number 3. My change is small. If I keep as much as I can in the functions.php with useful comments it will be easy to find what I’ve done and adjust it later if necessary. I don’t have to follow the original file hierarchy for my override to work. However, any of those methods would have been reasonable and valid. It was a matter of personal choice and technique. Whatever choice you make, be sure to include useful comments and documentation about what you did and why. You may have to look back at your code literally years later and have to remember where your head was today.

The original file had an if() statement to check the existence of the twentyfourteen_post_nav() function. I don’t see that this is important, so I just copy the functional code as-is. You can see that code block earlier in this article.

Getting everything to work, and overcoming my final confusion

Now it should be pretty straight-forward. I just need to add “TRUE” to the end of the function calls to next_post_link() and previous_post_link(). Easy! I got a little confused here, though. What confused me is an easy mistake for people who don’t do PHP every day, so I wanted to go over it. Here is my new version of the code with the changes emphasized. You’ll probably need to scroll the code to the right to see the changes. Again, my changes are emphasized.

<?php
function my_theme_enqueue_styles() {
$parent_style = 'twentyfourteen-style'; // This is 'twentyfourteen-style' for the Twenty Fourteen theme.

wp_enqueue_style( $parent_style, get_template_directory_uri() . '/style.css' );
wp_enqueue_style( 'child-style',
get_stylesheet_directory_uri() . '/style.css',
array( $parent_style ),
wp_get_theme()->get('Version')
);
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' );
/* Replacement twentyfourteen_post_nav() * Causes navigation to move within current category * Added TRUE to end of next_post_link() and previous_post_link() calls * cMw 11/5/2018 */ function twentyfourteen_post_nav() {
// Don't print empty markup if there's nowhere to navigate.
$previous = ( is_attachment() ) ? get_post( get_post()->post_parent ) : get_adjacent_post( false, '', true );
$next = get_adjacent_post( false, '', false );

if ( ! $next && ! $previous ) {
return;
}
?>
<nav class="navigation post-navigation" role="navigation">
<h1 class="screen-reader-text"><?php _e( 'Post navigation', 'twentyfourteen' ); ?></h1>
<div class="nav-links">
<?php
if ( is_attachment() ) :
previous_post_link( '%link', __( '<span class="meta-nav">Published In</span>%title', 'twentyfourteen' ), TRUE );
else :
previous_post_link( '%link', __( '<span class="meta-nav">Previous Post</span>%title', 'twentyfourteen' ), TRUE );
next_post_link( '%link', __( '<span class="meta-nav">Next Post</span>%title', 'twentyfourteen' ), TRUE );
endif;
?>
</div><!-- .nav-links -->
</nav><!-- .navigation -->
<?php
}
?>

What confused me was the placement of the TRUE statement. The solutions I found showed that you placed TRUE at the end, before the closing parenthesis of the function.

next_post_link( '%link', %title, TRUE );

That’s straightforward. However, in this usage the %title parameter is actually replaced by a call to the __() function. This provides a translation of the text so that people who have their browsers set for different languages will automatically see something useful. That’s a good idea. But it meant that I needed to place my statement on the back side of that function call, before the last parenthesis. 

next_post_link( '%link', __( '<span class="meta-nav">Next Post</span>%title', 'twentyfourteen' ), TRUE );

It’s obvious when you see it and think about it, but it can be easy to get lost in the sea of nested parentheses and brackets. I wasted a little time before I’d figured out what I was doing wrong. Maybe this will help save you time. This is a common usage in WordPress design, so just look carefully at the code and notice when function calls are used as a parameter. It’s like diagramming a sentence. You may have to visually break it down for yourself.

Conclusion

Everything works! My posts navigate within the category, just as I wish. I can easily make adjustments to anything on my theme’s functionality without disrupting the original theme. Updates will likely have no affect on what I’m doing.

I hope this saves someone a little trouble, and that you can see how I explored the code and documentation to decide the best approach for my needs. If something is unclear, let me know and I’ll try to address it. Happy WordPress hacking!

Leave a Reply

Your email address will not be published. Required fields are marked *