• Hi,

    I’m having an issue with custom taxonomies in that I need the entry for one custom taxonomy to be linked to the same entry in others.

    I am building a film review site and have set up taxonomies for Actors, Writers and Directors but of course, some people do both. At the moment, the taxonomies are all separate, so if someone clicks eg. Ben Affleck under the actor category, they will see a list of content where he is tagged as an actor.

    I’d like when a user on the front-end clicks on Ben Affleck in any category, they would see a list of everything he’s done as an actor, a writer and a director. Is it possible to link individual taxonomies across different sections like this? Or any way to have it appear that way on the front-end?

    Any help is much appreciated.

Viewing 15 replies - 1 through 15 (of 52 total)
  • @a_mulg it basically depends on how you have setup things on your site. If Actors, Writers and Directors are taxonomies then what’s the post type?

    Are you saving the data of Ben Affleck as term meta or post meta?

    Ben Affleck is saved a post or a term in your system?

    Regards

    Moderator bcworkz

    (@bcworkz)

    I’m assuming you are trying to get some sort of post_type, be it post, page, or a custom type, say “film”. WP_Query gets posts by default, but you can query any post_type or multiple types like “film”, “play”, “television”.

    To qualify the query by multiple taxonomies, use the tax_query argument. I expect you generally will want the relation to be OR. Using AND would mean an object would need all the terms assigned to the one object. While there are many examples where someone acted and directed, or wrote and directed, there are few where someone wrote, directed, and acted. OR is likely what you are after. Any objects that has any one or more terms applied.

    Thread Starter a_mulg

    (@a_mulg)

    Thanks for the replies.

    I currently have Trailers, Features and Reviews set up as custom post types, and I use Posts for news stories. All of these share the same custom taxonomies, which are currently Actors, Directors and Writers.

    I want to tag all Trailers, Features and Posts with the names of the talent involved so if you click on their tag, you see everything they’re tagged in.

    However with Reviews, I have the tags divided by Director, Writer and Actor so I can show them separately, but when I click on someone in the Actor column, I’d want to see everything they’ve done as a Writer and Director too.

    What I gather from what @bcworkz has said, is that I should use something along these lines…

    $args = array(
    	'post_type' => 'post',
    	'tax_query' => array(
    		'relation' => 'OR',
    		array(
    			'taxonomy' => 'actor',
    			'field'    => 'slug',
    			'terms'    => array( 'action', 'comedy' ),
    		),
    		array(
    			'taxonomy' => 'director',
    			'field'    => 'term_id',
    			'terms'    => array( 103, 115, 206 ),
    		),
                    array(
    			'taxonomy' => 'writer',
    			'field'    => 'term_id',
    			'terms'    => array( 103, 115, 206 ),
    		),
    	),
    );
    $query = new WP_Query( $args );

    Though I’m not entirely sure what values would go in Field and Terms?

    Moderator bcworkz

    (@bcworkz)

    If this particular query is to only return news stories, then the ‘post’ post_type argument is fine. If only reviews are wanted, supply only that post type’s slug. If a number of post types are wanted, supply an array of post type slugs.

    You separated out the reviews description from the others, but the criteria sounds the same to me — to get everything a particular person is tagged in. If so, your basic tax query structure is correct. What ever post type is requested is returned if any one or more of the sub-arrays is true.

    What you supply in fields and terms is up to you, but they should in all cases relate to the person that was clicked. The field is however you want to refer to each person from the links. The best fields IMO are either term_id or slug. Names are not too reliable due to possible case significance and ambiguous whitespace. Plus they are harder to validate. It’s simplest if the same field value is passed in the link’s URL. ID integers are easiest to validate, but slugs are more informative.

    Once sanitized and validated, the passed value is then used as the term argument in each sub-array.

    Thread Starter a_mulg

    (@a_mulg)

    Hi @bcworkz , thanks again for your help. Unfortunately I’m still having issues getting this to work. The code below is what I have at the moment but it doesn’t return any posts. (I’ve included the start of my loop in case that’s part of the problem).

    I’ve double checked the taxonomy and post type names and they seem to be correct so I must be going wrong somewhere else. Any guidance is very much appreciated.

    <?php
    
                $args = array(
                  'post_type' => 'reviews',
                  'tax_query' => array(
                  'relation' => 'OR',
                    array(
                      'taxonomy' => 'actors',
                      'field'    => 'term_id',
                      'terms'    => 'slug',
                    ),
                    array(
                      'taxonomy' => 'directors',
                      'field'    => 'term_id',
                      'terms'    => 'slug',
                    ),
                    array(
                      'taxonomy' => 'writers',
                      'field'    => 'term_id',
                      'terms'    => 'slug',
                    ),
                  ),
                );
                $the_query = new WP_Query( $args );
    
            ?>
    
             <?php if ( $the_query->have_posts() ) : while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
    Moderator bcworkz

    (@bcworkz)

    The problem would be your terms arguments. These should be values that are of the type in ‘field’ (i.e. values from that column in the wp_terms table). As term_ids are integers, the terms arguments should be integers. Specifically the term_id of the person clicked on by the user. Perhaps the link is something like this: <a href="example.com/media-star-archive/?act=1234&write=1324&direct=1432">Ben Affleck</a>

    media-star-archive is a page based on a template that runs the query you are working on. Naturally you would name it anything you want, this is just my example. The numbers after the ? are Ben’s term IDs in each taxonomy. Each number would be assigned to the corresponding terms argument. The numbers are available in $_GET. Be sure to validate each parameter sent because hackers could send malicious code in place of the proper values. Integers are easy to validate. $act = absint( (int) $_GET['act'] );

    Then use the validated variable in the proper sub-array:

    array(
       'taxonomy' => 'actors',
       'field'    => 'term_id',
       'terms'    => $act,
    ),

    Repeat for each term ID passed.

    Thread Starter a_mulg

    (@a_mulg)

    Ok, I’ve tried adding the specific number directly to the ‘terms’ part and it displays correctly on the front-end. However, when I try to use this line $act = absint( (int) $_GET['act'] );, it’s returning no results.

    I’m curious what the ‘act’ part is at the end of that string?

    What I have currently is now…

    $act = absint( (int) $_GET['act'] );
    
                $args = array(
                  'post_type' => 'reviews',
                  'tax_query' => array(
                  'relation' => 'OR',
                    array(
                      'taxonomy' => 'actors',
                      'field'    => 'term_id',
                      'terms'    => '$act',
                    ),
                  ),
                );
                $the_query = new WP_Query( $args );

    Apologies if I’m being dense, I don’t really know php and rusty when it comes to WordPress!

    Moderator bcworkz

    (@bcworkz)

    No worries, the ‘act’ in $_GET is strictly PHP, WP does not factor into this particular detail. It comes from the URL parameters of the clicked link, the part after the ‘?’. From my example: ?act=1234&write=1324&direct=1432

    URL parameters always have the form “separator name = value”. The names end up being the keys in $_GET that the value is assigned to. If you did var_dump( $_GET ); after that link was clicked, the output would be something like:

    array(3) {
      [act]=>
         int(1234)
      [write]=>
         int(1324)
      [direct]=>
         int(1432)
    }

    So $_GET[‘act’] is a way to get a specific value out of an associative array. The integer 1234 in this example. For it to have a value, you need to follow the link that contains the proper URL parameters.

    You can just add the URL parameters to whatever address you are using to execute your code in your browser’s address bar. You don’t actually have to click a link, though that is the ultimate goal. All that matters is the GET request has the proper URL parameters.

    Thread Starter a_mulg

    (@a_mulg)

    Ok, that makes sense but I’m still not understanding how to make 'terms' => '$act' show the integer that corresponds to the taxonomy that was clicked? If I use the integer directly like 'terms' => 268, , it displays posts correctly but I’m not sure how to make that dynamic.

    I wonder if the problem is with my URLs, as when I click a taxonomy from the front-end, the URL it displays is eg. /actors/ben-affleck/, with no integer involved.

    Moderator bcworkz

    (@bcworkz)

    Yup, that would be a problem. For this scheme to work, the URL is required to have the URL parameters I described appended onto the end. I was suggesting one possible way to get the needed data. There are several possibilities though. To dynamically append the URL parameters, some code is needed to determine the term IDs based on the current person. This can be done when the link is generated, but it could also be done just before our query code based on the requested person. Or, if the current object’s slug (ben-affleck in your example URL) will always correlate to each term’s slug, we can use that instead of term_id.

    The field to specify would then be “slug” instead of “term_id”. The value for ‘terms’ will then come from the current object. If the code determining the ‘terms’ value is within the page’s main loop, the value would be $post->post_name. If it hasn’t already been done, you may need to declare global $post;. If not in the loop, you can get the main queried object from get_queried_object().

    Whether it makes more sense to pass IDs in the URL or use the slug of the current object depends on the nature of the code in both the referring and requested page. It may be the relevant data is not available on the requested page, in which case the data needs to passed. If the data is always available on the requested page, there is no reason to pass the data.

    Since I’m not aware of these sort of details, I arbitrarily picked the pass the IDs concept as an example. It wasn’t my intent to present this as the only solution, I’m sorry if it came off that way. The best solution will use data already available, instead of needing to query for information (like getting the term_id given “ben-affleck”). The closer the data is to the code using it, the better. Local data is better than passing data.

    Since I don’t know these details, I cannot tell you the best approach that will work in all situations. I’ll be happy to help you correctly construct a proper query based on where and what data is available. Just tell me where and what that data consistently is for all situations where your code is used.

    Thread Starter a_mulg

    (@a_mulg)

    Hi @bcworkz, thanks again for your help. I’ll try to provide as much detail as possible.

    On my review page, I display separate lists of Directors, Writers and Actors that are specific to that particular film. The query that generates that list looks like this…

    $term_array = wp_get_post_terms($post->ID, 'directors', array("fields" => "ids"));
                        if ( is_array( $term_array ) ) {
                           $term_list = implode( ',', $term_array );
                           $args = array(
                              'taxonomy' => array( 'directors' ),
                              'format'   => 'flat',
                              'separator' => ", ",
                              'include' => $term_list,
                           ); 
    
                           wp_tag_cloud( $args );
                        }

    That is the directors one but I use the same one for Writers and Actors, just with ‘directors’ changed to ‘writers’ and so on.

    When I click one of those taxonomies, it takes me to what is at the moment my archive.php page, with the query that we’ve been discussing. Each slug is just the name that’s been entered in firstname-lastname format, which shouldn’t ever need to change. That’s what’s being used as the URL (generated by an SEO plugin). I think if this data can be used to build a working query, that’s probably suitable.

    I hope that makes sense? If you need any more info please let me know.

    Moderator bcworkz

    (@bcworkz)

    OK, thanks, that’s helpful. I think I get it now. Just to confirm, the actual request is for an archive list of posts that have the same taxonomy term/person that was clicked on? And the requested term/person slug matches up with the related term slugs in the other taxonomies, correct? And your goal is to have a list of reviews posts which have that term/person in any of the 3 taxonomies?

    If that’s all correct, then I think you will be better off altering the default query through the “pre_get_posts” action instead of discarding that and making a new custom query. You can probably then use your theme’s default archive.php template to generate the output. Or if you need to alter the output, maybe make an archive-reviews.php template that’s similar.

    Significantly, what ever template you use has no query, just the loop. The query is setup in the pre_get_posts callback. You use the WP_Query object’s set() method to alter the query arguments, or in this context, to alter the “query vars”. In the following examples, I’m assuming your callback is collecting the passed WP_Query object as $query. For anything dependent on existing query vars, you can get them with $query->get(). You’ll at least need to confirm the request is for a particular term/person in one of the 3 taxonomies before proceeding with altering the query, because all post queries pass through this action. Also confirm that $query->is_main_query() is true because all manner of secondary queries pass through as well. It’s a busy action!

    To check if the query is for a particular taxonomy term, use $query->get('tax-slug'); (replace tax-slug with your actual taxonomy slug) If anything but an empty string is returned, then the return is the term/person we want. Keep track of that value! Also keep track of which taxonomy it came in with. Before doing that, it may be a good idea to confirm $query->is_tax() and $query->is_archive() are both true. These can be combined in a conditional with $query->is_main_query() to determine if it’s worth trying to get taxonomy terms.

    Once you have the passed term and know it’s safe to alter the query, set the “post_type” query var to “reviews” and unset the “tax-slug” query var that had the term/person value, as we will be setting up a different kind of tax query. There’s no class method to unset query vars, but unset( $query->query_vars['tax-slug']); should work.

    Finally, set the “tax_query” query var to an array just like we had done previously for WP_Query. Except instead of term_id, the field will be slug and the terms will be the value you got earlier from the tax-slug query var, applied to each taxonomy.

    I’m sure this all sounds totally overwhelming and confusing. You’ll have to trust me when I say it’s not nearly as bad as it sounds. There’s a number of examples on the pre_get_posts doc page. Start with one of those for the basic structure. Put your code at the bottom of functions.php. Also put your previous work somewhere safe (but accessible – you’ll want to copy the tax_query you already started) and restore the original archive.php file.

    Between the examples and the tax_query, what you already have is actually the bulk of the code. Do the best you can, we can iron out any bugs next. Try to eliminate any syntax errors so you don’t get a white screen. If you haven’t yet defined WP_DEBUG as true on wp-config.php, you should do so, it’s helpful in fixing syntax errors.

    Thread Starter a_mulg

    (@a_mulg)

    Ok I’ve cobbled something together from my previous code and the examples on the pre get posts page but I’m not really sure if I’m on the right track, though I do know it isn’t working!

    What I have at the moment in my functions file is:

    function archive($query) {
    
        if ( ! is_archive() && $query->is_main_query() )
          return $query;
    
              $taxquery = array(
                'post_type' => 'reviews',
                'tax_query' => array(
                'relation' => 'OR',
                  array(
                    'taxonomy' => 'actors',
                    'field'    => 'slug',
                  ),
                ),
              );
    
          $query->set( 'tax_query', $taxquery );
            
        }
    
    add_action('pre_get_posts','archive');

    I have reverted my archive.php back to the default one and added the line <?php add_action( 'pre_get_posts', 'archive' ); ?> before <?php the_post(); ?>. Presumably this isn’t right and needs to be integrated into the loop somewhere?

    What I’m seeing on the front end as a result of this is a page that loads but doesn’t return any posts, and has the following error:

    Notice: Undefined offset: 0 in localhost\wp-includes\class-wp-query.php on line 3080

    That line is $this->post = $this->posts[$this->current_post];

    Sorry to be so slow on the uptake here, I’m still quite confused by a lot of this but hopefully I’m getting somewhere. At the moment I just don’t know if I’ve structured my query properly, and how I get that to pull in multiple taxonomies at once. I should also add that the ultimate goal is to display posts from across multiple post types as well, though I’m more concerned with the taxonomies!

    Moderator bcworkz

    (@bcworkz)

    That’s actually a good first effort! Well done, despite the fact it doesn’t work ?? I don’t know about other coders, but my code never runs the first time through. I imagine the error will go away once we get the query adjustments right.

    Querying for multiple post types is pretty simple, you pass an array of post types instead of just the one. $query->set( 'post-type', array('reviews', 'film', 'television',));

    Which brings us to another thing. We need to set each query var individually. Thus, remove the post_type argument from the $taxquery array and set it separately like I just illustrated.

    Unless these adjustments are to apply to all of your site’s archive output, you’ll be needing additional conditionals. Let’s focus on getting this request right for now, we can filter out other requests later.

    One little thing, since this is an action callback, any returned value is not collected, so there is no need for return $query; Just return; will do. The reason we can make a bunch of changes to $query and not have to return it is because $query is passed by reference, meaning we are working on the original object. Usually, parameters are passed by value, meaning we work on a copy. If the copy is not returned, nothing will change. OTOH, returning a value that is not used does no harm, other than showing that the coder doing so doesn’t understand action callbacks ??

    The most important thing we haven’t established is getting the right term/person out of the query. It could be in any of the three taxonomies, so we need something like the following for each taxonomy:

    $tentative = $query->get('actors'); //correct taxonomy slug required here
    if ('' != $tentative ) $person = $tentative;

    Once we get the person requested, we can use it in the tax_query.

    array(
       'taxonomy' => 'actors',
       'field'    => 'slug',
       'terms'    =>  $person,
    ),

    Make an array for each taxonomy, using $person for terms in each case. The syntax for the one tax array you have is perfect, other than missing the terms argument. It’s position and relationship to other elements is perfect. The other two arrays go into the same level as the first, so the outer array has 4 elements, the relation => OR and 3 taxonomy arrays.

    However, the assignment to $taxquery is wrong, you don’t want a ‘tax_query’ element in the array because it will be specified in $query->set(). Essentially, remove this part of your current code:

    array(
       'post_type' => 'reviews',
       'tax_query' =>

    so you end up with

    $taxquery = array(
       'relation' => 'OR',
       // the three taxonomy arrays go here
    );

    Now, if you follow a link similar to httр://example.com/actors/ben-affleck/ you should get all whatever type posts that have Ben as any of the 3 taxonomy terms.

    However, other archive requests that do not involve reviews or related taxonomy terms that were working will now be broken. One thing that will help is to add a line after the attempts at getting $person that does if ( ! isset( $person )) return; This aborts our code if a term of one of the three taxonomies is not requested. Between this and what you already have, I think this may fix everything!

    If not, add print_r( $query->query_vars ); and compare the list for a proper taxonomy term request against the list for the broken request. Find out what’s different between the two. The differences can be used to qualify when our adjustments should be applied.

    Thread Starter a_mulg

    (@a_mulg)

    I think I’m getting there, though I’m not quite understanding this piece of code:

    $tentative = $query->get('actors'); //correct taxonomy slug required here
    if ('' != $tentative ) $person = $tentative;

    So I need to add it three times, for the three different taxonomies, but should I be giving each one separate IDs (instead of tentative and person, for example)?

    What I have is currently this, although it isn’t returning posts, it is returning the error Undefined variable: person

    function archive($query) {
    
        if ( ! is_archive() && $query->is_main_query() )
          return
    
            $query->set( 'post-type', array( 'post' , 'reviews' , 'trailer' , 'feature' ));
            
            $tentative = $query->get('actors');
              if ('' != $tentative ) $person = $tentative;
            
            $tentative = $query->get('writers');
              if ('' != $tentative ) $person = $tentative;
    
            $tentative = $query->get('directors');
              if ('' != $tentative ) $person = $tentative;
    
              $taxquery = array(
                'relation' => 'OR',
                  array(
                     'taxonomy' => 'actors',
                     'field'    => 'slug',
                     'terms'    =>  $person,
                  ),
                  array(
                     'taxonomy' => 'writers',
                     'field'    => 'slug',
                     'terms'    =>  $person,
                  ),
                  array(
                     'taxonomy' => 'directors',
                     'field'    => 'slug',
                     'terms'    =>  $person,
                  ),
              );
    
          $query->set( 'tax_query', $taxquery );
            
        }
    
    add_action('pre_get_posts','archive');

    I’ve also noticed that I’m not seeing posts on any page now, just getting a ‘sorry, no posts available’ message, though presumably once this query is formatted correctly it should probably be fine!

Viewing 15 replies - 1 through 15 (of 52 total)
  • The topic ‘Linking custom taxonomies’ is closed to new replies.