0

I'm trying to add an incremental variable to each post that comes up in a series of queries. The purpose is to calculate a weight for each result in order to be able to order everything later.

This is what I've come up with so far, but I'm doing something wrong, as the weights seem to add up globally instead of for each particular post.

// set the variables
$author_id = get_the_author_meta('ID');
$tags_id = wp_get_post_tags($post->ID);  $first_tag = $tags_id[0]->term_id;
$categories_id = wp_get_post_categories($post->ID);
$weight = 0;

// loop for same tag
$by_tag = new WP_Query(array(
'tag__in' => $first_tag,
'posts_per_page' => '5'
));

// attempting to assign values to posts - unsuccessfully
if ($by_tag):
foreach ($by_tag as $post):
setup_postdata($post->ID);
$weight += 2;
endforeach;
endif;

// add ids to array
if ( $by_tag->have_posts() ) {
while ( $by_tag->have_posts() ) {
$by_tag->the_post(); 
$add[] = get_the_id(); 
}}

// loop for same category
$by_category = new WP_Query(array(
'category__in' => $categories_id,
'posts_per_page' => '5'
));

// same as before
if ($by_category):
foreach ($by_category as $post):
setup_postdata($post->ID);
$weight += 1;
endforeach;
endif;

// add ids to array
if ( $by_category->have_posts() ) {
while ( $by_category->have_posts() ) {
$by_category->the_post(); 
$add[] = get_the_id(); 
}}

// loop array of combined results 
$related = new WP_Query(array(
'post__in' => $add,
'post__not_in' => array($post->ID),
'orderby' => $weight,
'order'   => 'DESC',
'posts_per_page' => '10'
));

// show them
if ( $related->have_posts() ) {
while ( $related->have_posts() ) {
$related->the_post(); 
// [template]
}}
4
  • 1
    Problems at a glance: $weight is just a variable that you increment occasionally - you never associate it's value with any post. $by_tag and $by_category don't hold query results - they hold WP_Query instances (i.e. if($by_tag) will always be truthy, and foreach($by_tag as $post) doesn't make sense). $add is never defined, so pushing items onto it as though it were an array with $add[] = get_the_id() may not work. $weight holds a single integer value, so 'orderby' => $weight, doesn't make sense (i.e., you're telling WP to order by 5, for example).
    – bosco
    Commented Jul 21, 2016 at 0:10
  • 1
    Also, the setup_postdata() function takes a post object as an argument, not an ID - but in either scenario you shouldn't need this function. I'd recommend running through the basic debugging setup process if you haven't already - many of these problems should be producing errors.
    – bosco
    Commented Jul 21, 2016 at 0:25
  • Indeed, I'm pretty much aware that the foreach part is complete junk (left it there rather as a placeholder) I just don't have any idea how to go about it or if it's even possible.
    – lucian
    Commented Jul 21, 2016 at 0:36
  • Definitely possible =] . The general idea is 1) make an array that associates post IDs with their weights 2) when a post shares a term with your subject, increase it's value in the array 3) re-organize the queried array of posts based on the values in the weights array. I made an implementation below... but I admit I got a little over-excited and made it more complicated than necessary. Still, I think it'll give you some ideas.
    – bosco
    Commented Jul 21, 2016 at 3:21

2 Answers 2

1

It's a neat idea!

The way I would tackle the problem is to create an associative array of post IDs mapped to their individual weights. Since weights are subjective - that is, the represent the value of other posts' relationships with the post currently being displayed - it doesn't make much sense to store these weights in the database each time you process these relationships, and thus we can't use WP_Query's ordering arguments to arrange the resulting posts; so instead you'll need to re-arrange them based on the sorted array of weights.

Unfortunately, since we can't determine how much weight any single related post has prior to querying taxonomies, to calculate the most "valuable" posts we have to query all related posts - that is to say, using queries with arguments that limit the number of results won't work as you can't definitively know that those returned with have high values.

This makes this related posts operation somewhat expensive, computationally - ideally you'd eventually improve the process to store each post's most relevant related posts and update them on occasion. This could be done by storing them in the subject post's meta-data and only updating that list with a scheduled operation, or by storing related posts as transient data and then only update that list when the transient expires.


Implementation

The (completely untested, likely not-quite-functional) function below takes a post object (representing the "subject" post) and an array mapping taxonomy names to integer values (representing how much "weight" posts should receive for sharing a term in that taxonomy with the subject). It returns a WP_Query instance with a posts array sorted according to calculated weights.

It's somewhat different from what you are after, I think, as it takes all of the subject's terms into consideration (which also makes it more computationally expensive) - but it should give you some ideas.

By building a 'tax_query' argument for the WP_Query instance out of all relevant taxonomies, you're able to use one query to obtain every post that shares a relevant term with the subject instead of one for each taxonomy. It makes the code a little more confusing than using tag and category-related functions and WP_Query arguments - but reducing the number of queries is generally a worthwhile optimization.

function wpse_232759_get_term_related_query( $post = null, $shared_taxonomy_values = null, $query_args = [] ) {
  $subject                = get_post( $post );
  $subject_id             = $subject->ID;
  $subject_taxonomies     = get_object_taxonomies( $subject );
  $subject_term_ids       = [];
  $post_weights           = [];

  if( !isset( $shared_taxonomy_values ) ) {
    $shared_taxonomy_values = [
      'category' => 1, // Sharing a category is worth 1 point by default
      'post_tag' => 1  // Sharing a tag is worth 1 point by default
    ];
  }

  // Build a WP_Query 'tax_query' argument to find every post that shares a tag or category
  $shared_term_tax_query = [ 'relation' => 'OR' ];
  foreach( $shared_taxonomy_values as $taxonomy_name => $shared_term_value ) {
    // If the subject doesn't actually use this taxonomy, move on
    if( !in_array( $taxonomy_name, $subject_taxonomies ) )
      continue;

    // Record the subject's term ids in this taxonomy
    $subject_term_ids[ $taxonomy_name ] => array_map( function( $term ) {
        return $term->term_id;
      },
      get_the_terms( $subject, $taxonomy_name )
    );

    // Add to the tax_query
    $shared_term_tax_query[] = [
      'taxonomy' => $taxonomy_name,
      'terms'    => $subject_term_ids[ $taxonomy_name ]
    ];
  }

  // Add generated query args to any supplied by the user
  $query_args[ 'post__not_in' ] = $subject_id; // Ignore the subject
  $query_args[ 'tax_query' ]    = $shared_term_tax_query;

  // Get all posts with a shared term in a taxonomy with value
  $term_related_query = new WP_Query( $query_args );

  // Increment the weight of each related post for every term it shares
  foreach( $tax_related_query->posts as $object ) {
    foreach( $shared_taxonomy_values as $taxonomy_name => $shared_term_value ) {
      // Get an array of this post's term ids in this taxonomy
      $object_tax_term_ids = array_map( function( $term ) {
          return $term->term_id;
        },
        get_the_terms( $object, $taxonomy_name )
      );

      // Create an array containing the term ids for this taxonomy that both the subject and this post share
      $shared_tax_term_ids = array_intersect( $subject_term_ids[ $taxonomy_name ], $object_tax_term_ids );

      // If this post doesn't have a weight yet, create it and set it to "0"
      if( !isset( $post_weights[ $object->ID ] ) )
        $post_weights[ $object->ID ] = 0;

      // Add to this post's weight the value for a shared term in this taxonomy multiplied by the number of shared terms
      $post_weights[ $object->ID ] += $shared_term_value * count( $shared_tax_term_ids );
    }
  }

  // Re-arrange the related posts array based on the calculated weights by using a custom sorting function
  $term_related_query->posts = usort( $term_related_query->posts, function( $a, $b ) {
    if( $post_weights[ $a->ID ] === $post_weights[ $b->ID ] )
      return 0; // The posts have the same weight
    
    if( $post_weights[ $a->ID ] > $post_weights[ $b->ID ] )
      return 1; // Post $a has a larger weight than post $b

    return -1; // Post $b has a larger weight than post $a
  } );

  // Return the related posts query with modified post order
  return $term_related_query;
}

The function can then be employed as such:

// Get a term-related WP_Query object with posts organized by weight,
// where a shared category is worth 1 pt and a shared tag worth 2
$related_query = wpse_232759_get_term_related_query( $post, [
  'category' => 1,
  'post_tag' => 2
] );

// Loop through all term-related posts - display the 5 with the highest weights
if( $related_query->have_posts() ) {
  while( $related_query->have_posts() && $related_query->current_post < 5 ) {
    $related_query->the_post();
    ?>

      <!-- Related post markup -->

    <?php
  }
}
3
  • I'm trying to work this out, it's a bit complicated for me :D In the mean time, I was wondering if there was a way to just add[] ids to the array as key with the weight as value and use those pairs for the query.
    – lucian
    Commented Jul 21, 2016 at 11:32
  • Certainly! That's what's going on with the $post_weights variable in my function - at least I think. What do you mean by "use those pairs for the query?" I'll add a less obscure example later today =]
    – bosco
    Commented Jul 21, 2016 at 18:53
  • Just got it working with $weight[$post->ID] += 2; etc instead of what I had, then arsorting everything - posting the full code below.
    – lucian
    Commented Jul 21, 2016 at 19:09
1

Here's a full working code for whomever may need it:

$tags_id = wp_get_post_tags($post->ID);  $first_tag = $tags_id[0]->term_id;
$categories_id = wp_get_post_categories($post->ID); $first_category = $categories_id[0];

$by_tag = new WP_Query( array( 'post__not_in' => array($post->ID), 'tag__in' => $first_tag ) ); 
if ( $by_tag->have_posts() ) { while ( $by_tag->have_posts() ) { $by_tag->the_post();
$add[] = get_the_id();
$weight[$post->ID] += 2;
wp_reset_query(); }}

$by_category = new WP_Query( array( 'post__not_in' => array($post->ID), 'category__in' => $categories_id ) );
if ( $by_category->have_posts() ) { while ( $by_category->have_posts() ) { $by_category->the_post();
$add[] = get_the_id();
$weight[$post->ID] += 1;
wp_reset_query(); }}

arsort($weight);
foreach ($weight as $key => $value) {   $posts_ordered[] = $key; }
$related = new WP_Query( array( 'post__in' => $posts_ordered,  'orderby' => 'post__in','posts_per_page' => '60') );
if ( $related->have_posts() ) { while ( $related->have_posts() ) { $related->the_post(); 
// template
}}
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.