Search code examples
phpwordpresscustom-taxonomytaxonomy-terms

Custom Taxonomies and how to stay in them when the posts have multiple terms selected?


Wondering if anyone can help me think this one out?

Whilst in lock-down, I've been putting together a simple site to organise my collection of Retro Gaming Adverts covering systems from the Atari 2600 up to the N64. I've still got a few 1000 to add to the site (takes time) but i've come across an issue I'm not sure how to implement a fix for.

You can browse the adverts by system through their single post pages but if an advert covers multiple systems it messes up the previous and next posts link and will drop you into another system.

For Example: If you're using the next and previous post links to go through the "mega drive / genesis" section once you get to " Battletoads and Double Dragon " when you press the next post arrow this time you're suddenly going through adverts tagged as NES, due to the fact thats the first term associated with it.

See : https://www.retrogameads.com/system/mega-drive/ and click on the first advert, then keep pressing the next arrow and you'll see what i mean.

I guess I could post each advert multiple times for each system but I don't like the idea of that.

Anyone got any suggestions on how I could work out what Term the user was browsing and keep them in that one?

Bare in mind this site is a work in progress so the design is just something basic till i work out the best way to organise things.

Let me know your thoughts.

Update: Current method for getting prev next posts...

<?php
$terms = get_the_terms( $post->ID, 'system' ); 
$i = 0;
$systems = array();
foreach ( $terms as $term ) {
    $systems[$i] = $term->slug;
    $i++;
}
$postlist_args = array(
    'posts_per_page'  => -1,
    'post_type'       => 'portfolio',
    'system'          => $systems[0],
    'order'          => 'ASC',
    'orderby'        => 'title'
); 
$postlist = get_posts( $postlist_args );
$ids = array();
foreach ($postlist as $thepost) {
    $ids[] = $thepost->ID;
}
$thisindex = array_search($post->ID, $ids);
$previd = $ids[$thisindex-1];
$nextid = $ids[$thisindex+1];
?>
<div class="prev_next">
<?php
if ( !empty($previd) ) {
    echo '<div class="older"><a rel="prev" href="' . get_permalink($previd). '">&lsaquo;</a></div>';
}
if ( !empty($nextid) ) {
    echo '<div class="newer"><a rel="next" href="' . get_permalink($nextid). '">&rsaquo;</a></div>';
}
?>

Solution

  • When you click on an advert, you're essentially visiting the single page for that advert. At that moment, WP does not know/remember that the user only wants to see adverts from the system you clicked.

    How are you rendering the previous/next links right now?

    One possible solution could be to add a parameter to the URL and then take this into account in the PHP code when rendering the previous/next links. (/portfolio/advert-name/?system=mega-drive for example)

    Edit: Of course the URL could also be made prettier... if you want to put in some extra work, you could register a permalink of the form /portfolio/mega-drive/advert-name/ for example. But this would require a bit more work.

    Would that work? I think it would be the best solution considering the alternatives.

    Update: For the actual implementation, the get_next_post_where and get_previous_post_where filters might prove to be very useful. You could make it so that it takes the system parameter into account for the previous/next links.

    Another option: You could also set up a PHP session and remember the current system that way, but then you will require a PHP session, which is not a good thing, and it will also prevent you from using full page caching (performance optimization).

    Yet another option would be to remember the current system client-side through a cookie. But in that case you have caching issues again unless you load the previous/next links through AJAX.


    After reading the update on your question, I have the following notes:

    • system is not a valid argument and will be ignored on the get_posts() call.
    • the method you use to get the previous and next posts is very inefficient, because you query the whole database, and then you save everything in memory, and then you comb through the whole result set in memory, using a lot of unnecessary ram and CPU power

    So how can we improve this?

    Simple: Use the get_previous_post and get_next_post functions on the $post. It accepts three arguments. Here is the example for get_next_post (but get_previous_post is basically the same):

    get_next_post( bool $in_same_term = false, array|string $excluded_terms = '', string $taxonomy = 'category' )
    

    Now if you set $in_same_term to true then the next post will be of a post that shares at least one taxonomy term (system in our case).

    You also have to set $taxonomy to system because that is the custom taxonomy.

    And the other parameter is $excluded_terms. Too bad there is no $included_terms otherwise we could just put the system we want in there. But we can do it another way... We can filter out the current system (in the system GET parameter in our URL) and keep all other systems as systems to exclude.

    So let's build what we need now.

    global $post;
    
    // What system did we come from?
    $system = $_GET['system'] ?? null; // Set to null if nothing was specified (uses PHP's null coalescing operator available since PHP 7
    
    // Exclude nothing by default
    $excluded_term_ids = [];
    
    // If we came here by clicking on a system, then exclude all other systems so the previous/next links will be of the same system only
    if ($system) {
      // Retrieve system terms for this post
      $terms = get_the_terms( $post, 'system' );
    
      // Filter the list of terms to remove the system we came from
      // This way we can create a list of terms to exclude
      $excluded_terms = array_filter( $terms, function ( $term ) use ( $system ) {
          return $term->slug != $system;
      } );
    
      // The get_previous_post and get_next_post functions expect $excluded_terms to be an array of term IDs, so we must map the WP_Term objects into IDs
      $excluded_term_ids = array_map( function ( $term ) {
          return $term->term_id;
      }, $excluded_terms );
    }
    
    // Retrieve previous and next post
    $previous_post = $post->get_previous_post( true, $excluded_term_ids, 'system' );
    $previous_post = $post->get_next_post( true, $excluded_term_ids, 'system' );
    
    // Echo out the page links
    // And don't forget to re-add the ?system= parameter to the URL
    $url_suffix = $system ? ('?system=' . $system) : '';
    if ( $previous_post ) {
        echo '<div class="older"><a rel="prev" href="' . get_permalink($previous_post) . $url_suffix . '">&lsaquo;</a></div>';
    }
    if ( $next_post ) {
        echo '<div class="newer"><a rel="next" href="' . get_permalink($next_post) . $url_suffix . '">&rsaquo;</a></div>';
    }
    

    NOTE: Untested code.. So there might be a small bug somewhere.. Let me know if you encounter an issue with this code..

    Important: On the page of a system, you must now also add '?system=current-system' to the URLs that you render. Probably something like: echo get_permalink($advert) . '?system=' . $post->post_name (could be different depending on how your code is on that screen..)