Keep Pagination in the Same Taxonomy

Header image for Keep Pagination in the Same Taxonomy

Join your posts together by category (or tag), rather than relying on the default published date order when paging forwards or backwards.

5 minute reading time of 1064 words (inc code) Code available at codeberg.org and the last commit was Plugin available at WordPress

Intro

So, for example, if you’ve written a series of “how to do X” articles over a period of time, this plugin will let you easily link them all together by virtue of sharing a term in any taxonomy.

Aside from the usual admin code (adding the config for this plugin to the existing Reading settings page), we use two hooks; one for the <link rel="prev"> and <link rel="next"> links generated by WP in the HTML head, and one for the visible Previous/Next links that would get displayed after the Post.

add_action('get_header', array($this, 'same_pagination'));
add_action('the_post', array($this, 'same_pagination'));

same_pagination()

The common function that only alters the relevant SQL when we’re on a webpage displaying a single post that has a taxonomy that we’ve previously chosen in the admin area.

public function same_pagination($post = null) {
    if (! is_singular()) return; // bail if we're not looking at a single post
    if (is_null($post)) global $post; // need this for the get_header call here

    // get the taxonomies we've ticked
    $selected = array_filter($this->settings);
    // and the ones this post has
    $taxonomy_names = array_flip(get_object_taxonomies($post));
    // then see which ones apper in both lists
    $matching_taxonomies = array_intersect($selected, $taxonomy_names);
    
    if (sizeof($matching_taxonomies) > 0) {

        // single request only cache (ie. caches the get_header call for use in the_post)
        $taxonomy_termids = wp_cache_get($post->ID.'_kpist_taxonomy_termids');

        if ($taxonomy_termids === false) {
            $taxonomy_termids = array();

            // retrieve all the terms this post has in the taxonomies we're interested in
            $terms = wp_get_object_terms($post->ID, array_keys($matching_taxonomies));
            // list_pluck can't handle multiple values per key
            $terms = wp_list_pluck($terms, 'taxonomy', 'term_id');
            // so we need to turn them into an array ourselves
            foreach ($terms as $term_id => $taxonomy) {
                if (! isset($taxonomy_termids[$taxonomy])) $taxonomy_termids[$taxonomy] = array();
                $taxonomy_termids[$taxonomy][] = $term_id;
            }

            wp_cache_add($post->ID.'_kpist_taxonomy_termids', $taxonomy_termids);
        }

        $this->taxonomy_termids = $taxonomy_termids;

        add_filter('get_next_post_join', array($this, 'filter_join_clause'), 10, 5);
        add_filter('get_previous_post_join', array($this, 'filter_join_clause'), 10, 5);
        add_filter('get_next_post_where', array($this, 'filter_where_clause'), 10, 5);
        add_filter('get_previous_post_where', array($this, 'filter_where_clause'), 10, 5);
    }

}

filters

Prepare our SQL joins for the following WHERE clause…

public function filter_join_clause($join, $in_same_term, $excluded_terms, $taxonomy, $post) {
    global $wpdb;

    $until = sizeof($this->taxonomy_termids);
    for ($i=0; $i<$until; $i++) {
        $join.= " INNER JOIN {$wpdb->term_relationships} AS kpist_tr{$i} ON p.ID = kpist_tr{$i}.object_id 
                  INNER JOIN {$wpdb->term_taxonomy} AS kpist_tt{$i} ON kpist_tr{$i}.term_taxonomy_id 
                                                                     = kpist_tt{$i}.term_taxonomy_id";
    }

    return $join;
}

And here, filter the get_adjacent_post query accordingly.

public function filter_where_clause($where, $in_same_term, $excluded_terms, $taxonomy, $post) {
    global $wpdb;

    $clauses = array(); $i = 0;
    foreach ($this->taxonomy_termids as $taxonomy => $term_ids) {
        // not that we use them, but if anything else is asking to remove terms, then honour that here
        $term_ids = array_map('intval', array_diff($term_ids, (array) $excluded_terms));
        // then check we have some terms left to try matching
        if (sizeof($term_ids)) {
            $clauses[]= $wpdb->prepare("(kpist_tt{$i}.term_id IN (".implode(',', $term_ids).") 
                                     AND kpist_tt{$i}.taxonomy = %s)", $taxonomy);
        }
        $i++;
    }

    if (sizeof($clauses)) {
        // join the taxonomy clauses together
        switch ($this->settings['kpist_toggle']) {
            case 'any': $toggle = ' OR ';
                        break;
            case 'all':
            default:    $toggle = ' AND ';
        }
        $where.= ' AND ('. implode($toggle, $clauses). ')';
    }

    return $where;
}

Note: the final SQL query made by WP does not contain a GROUP BY clause - and nor is there a direct filter to call - so if a post has multiple taxonomies in common, then we’ll return multiple identical Post IDs. Fortunately, it does have an ORDER BY and a LIMIT clause, so it’s not (yet) a problem.


Reply via email