Using the loop_end Hook With Multiple WordPress Loops
I recently added some custom navigation to the tag and category pages on my blog. I could have tweaked the tag template on the theme I’m using, or I could have created a child theme (which seems overkill for something this small). Since I already use a functionality plugin for the custom code I use on this site, it made more sense to add it there using WordPress hooks. That also means the next time I switch themes the new links will still be there.
The Problem
The clear approach was to use the loop_end hook, which runs when WordPress (or ClassicPress) finishes the Loop with all the posts for the current page. And it worked! Sort of. It added the link twice, once at the top of the page and once where it was supposed to.
It turned out one of the other plugins I use runs through the Loop when building preview cards, then rewinds it so the theme templates can display posts normally. So I had
The Solution
OK, how do I tell my custom function to only run on one iteration of the Loop, and which one?
After searching the WordPress documentation, and searching the web, and not finding anything remotely useful for combining loop_end and rewind_posts() (never mind any suggestion for best practices), I decided to go with an old standby: Set a global variable between Loops, and check that variable in my function.
Where to set the global variable? In this case, the first Loop runs in the HTML head, so I was able to use the wp_body_open hook to change the variable state. (If you have multiple Loops running in the page body, you’ll need to look for another dividing point.)
Now, even though loop_end still gets called twice, my function only adds those links where it’s supposed to. And I won’t have to change it if I remove the plugin that’s running the extra Loop, or change themes (unless the new theme adds more passes through the Loop), or even switch between ClassicPress and WordPress.
Sample code
// Hack to only run my loop_end function when the Loop is running in the page body,
// since it gets run early when building the <head>
global $exampleprefix_building_page;
$exampleprefix_building_page = false;
function exampleprefix_set_building_page() {
$GLOBALS['exampleprefix_building_page'] = true;
}
add_action( 'wp_body_open', 'exampleprefix_set_building_page' );
// Add link at the end of tag/category archives.
add_action( 'loop_end', 'exampleprefix_tag_fullsite' );
function exampleprefix_tag_fullsite( $query ) {
if ( $GLOBALS['exampleprefix_building_page'] && $query->is_main_query() && ( $query->is_tag() || $query->is_category() ) ) {
$obj = $query->get_queried_object();
$slug = $obj->slug;
echo '<p><a rel="nofollow" href="https://example.com/tag/' . $slug . '">All pages site-wide with this tag</a></p>';
}
}