Categories
How To

Rolling Your Own Related Posts

Related Posts that are extendable and customizable are within your reach!

To start out at the top, I did not write a whole ‘related posts’ plugin. As with all things, I started by asking myself “What’s the problem I’m trying to solve?”

The answer is “I have a custom post type that needs to relate to other posts in the type, but based on my specific criteria which is currently organized into custom taxonomies and post meta.” And from the outset, that certainly sounds like a massive custom job. it was one I was dreading until I remembered that a developer I respected and trusted had once complained to me about the problems with all those other auto-related-posts plugins.

  1. They’re heavy and use a lot of ram
  2. They don’t let you customize ‘weight’ of relations
  3. They’re not extendable

So I did the next logical thing and I looked up their plugins.

The Plugin The Plugin

The crux of why I chose this plugin was simply that it’s extendable, but also that it started out with what I had:

Posts with the most terms in common will display at the top!

Perfect!

Top ↑

Design The Basics Design The Basics

Before you jump into coding, you need to know what you’re doing. I chose to isolate what I needed first. I made a list of everything I thought was relative:

  • Taxonomies: Tropes, Genres, Intersectionality, Tags, Stars
  • Post Meta: Worth It, Loved, Calculated Score

Yes, it’s that site again.

I read the plugin documentation and verified that for most of that I just needed to list the taxonomies in the shortcode like this:

[related_posts_by_tax fields="ids" order="RAND" title="" format="thumbnails" image_size="postloop-img" link_caption="true" posts_per_page="6" columns="0" post_class="similar-shows" taxonomies="lez_tropes,lez_genres,lez_stars,lez_intersections,lez_showtagged"]

Initially I didn’t list the stars because the way the code works, it would say “If you have a Gold Star, show other Gold Stars.” And that wasn’t what I wanted to see. I wanted “If you have ANY star, show other shows with a star.” That said, once we got over 12 shows in each ‘star’ category, this became much easier to match and I could add it in.

The rest of the code, those checks for meta, needed actual code written.

Top ↑

Meta Checks Meta Checks

There’s a helpful filter, related_posts_by_taxonomy_posts_meta_query, that lets you filter the meta queries used by the post. Leveraging that, we can make our checks:

  1. Match the ‘worth it’ value of a show
  2. If the show is loved, list other loved show
  3. If the show isn’t loved, use the score to find show with the same relative value

Both Worth It and Loved are post meta values. Mine happen to be added by CMB2, but the logic remains the same regardless how you add it. Worth It has four possible values (Yes, No, Maybe, TBD), and the check is either the value or false. Loved is a checkbox, a boolean exists or not, which means it’s a true/falsy. The score is a number that’s generated every time the show is saved, and it’s crazy complicated and another story.

The code I use looks like this:

add_filter( 'related_posts_by_taxonomy_posts_meta_query', 'MYSITE_RPBT_meta_query', 10, 4 );
function MYSITE_RPBT_meta_query( $meta_query, $post_id, $taxonomies, $args ) {
	$worthit = ( get_post_meta( $post_id, 'lezshows_worthit_rating', true ) ) ? get_post_meta( $post_id, 'lezshows_worthit_rating', true ) : false;
	$loved   = ( get_post_meta( $post_id, 'lezshows_worthit_show_we_love', true ) ) ? true : false;
	$score   = ( get_post_meta( $post_id, 'lezshows_the_score', true ) ) ? get_post_meta( $post_id, 'lezshows_the_score', true ) : 10;

	// We should match up the worth-it value as well as the score.
	// After all, some low scores have a thumbs up.
	if ( false !== $worthit ) {
		$meta_query[] = array(
			'key'     => 'lezshows_worthit_rating',
			'compare' => $worthit,
		);
	}

	// If the show is loved, we want to include it here.
	if ( $loved ) {
		$meta_query[] = array(
			'key'     => 'lezshows_worthit_show_we_love',
			'compare' => 'EXISTS',
		);
	}

	// If they're NOT loved, we use the scores for a value.
	if ( ! $loved ) {
		// Score: If the score is similar +/- 10
		if ( $score >= 90 ) {
			$score_range = array( 80, 100 );
		} elseif ( $score <= 10 ) {
			$score_range = array( 10, 30 );
		} else {
			$score_range = array( ( $score - 10 ), ( $score + 10 ) );
		}
		$meta_query[] = array(
			'key'     => 'lezshows_the_score',
			'value'   => $score_range,
			'type'    => 'numeric',
			'compare' => 'BETWEEN',
		);
	}

	return $meta_query;
}

Top ↑

More Similar More Similar

But there’s one more thing we wanted to include. When I built this out, Tracy said “There should be a way for us to pick the shows we think are similar!”

She’s right! I built in a CMB2 repeatable field where you can pick shows from a dropdown and that saves the show post IDs as an array. That was the easy part, since we were already doing that in another place.

Once that list exists, we grab the handpicked list, break it out into a simple array, check if the post is published and not already on the list, and combine it all:

add_filter( 'related_posts_by_taxonomy', array( $this, 'alter_results' ), 10, 4 );
function alter_results( $results, $post_id, $taxonomies, $args ) {
	$add_results = array();

	if ( ! empty( $results ) &amp;&amp; empty( $args['fields'] ) ) {
		$results = wp_list_pluck( $results, 'ID' );
	}

	$handpicked  = ( get_post_meta( $post_id, 'lezshows_similar_shows', true ) ) ? wp_parse_id_list( get_post_meta( $post_id, 'lezshows_similar_shows', true ) ) : array();
	$reciprocity = self::reciprocity( $post_id );
	$combo_list  = array_merge( $handpicked, $reciprocity );

	if ( ! empty( $combo_list ) ) {
		foreach ( $combo_list as $a_show ) {
			//phpcs:ignore WordPress.PHP.StrictInArray
			if ( 'published' == get_post_status( $a_show ) &amp;&amp; ! in_array( $a_show, $results ) &amp;&amp; ! in_array( $a_show, $add_results ) ) {
				$add_results[] = $a_show;
			}
		}
	}

	$results = $add_results + $results;

	return $results;
}

But … you may notice $reciprocity and wonder what that is.

Well, in a perfect world if you added The Good Fight as a show similar to The Good Wife, you’d also go back and add The Good Wife to The Good Fight. The reality is humans are lazy. There were two ways to solve this reciprocity of likes issues.

  1. When a show is added as similar to a show, the code auto-adds it to the other show
  2. When the results are generated, the code checks if any other show likes the current show and adds it

Since we’re already having saving speed issues (there’s a lot of back processing going on with the scores) and I’ve integrated caching, it was easier to pick option 2.

function reciprocity( $post_id ) {
	if ( ! isset( $post_id ) || 'post_type_shows' !== get_post_type( $post_id ) ) {
		return;
	}

	$reciprocity      = array();
	$reciprocity_loop = new WP_Query(
		array(
			'post_type'              => 'post_type_shows',
			'post_status'            => array( 'publish' ),
			'orderby'                => 'title',
			'order'                  => 'ASC',
			'posts_per_page'         => '100',
			'no_found_rows'          => true,
			'update_post_term_cache' => true,
			'meta_query'             => array(
				array(
					'key'     => 'lezshows_similar_shows',
					'value'   => $post_id,
					'compare' => 'LIKE',
				),
			),
		)
	);

	if ( $reciprocity_loop->have_posts() ) {
		while ( $reciprocity_loop->have_posts() ) {
			$reciprocity_loop->the_post();
			$this_show_id = get_the_ID();
			$shows_array  = get_post_meta( $this_show_id, 'lezshows_similar_shows', true );

			if ( 'publish' === get_post_status( $this_show_id ) &amp;&amp; isset( $shows_array ) &amp;&amp; ! empty( $shows_array ) ) {
				foreach ( $shows_array as $related_show ) {
					if ( $related_show == $post_id ) {
						$reciprocity[] = $this_show_id;
					}
				}
			}
		}
		wp_reset_query();
		$reciprocity = wp_parse_id_list( $reciprocity );
	}

	return $reciprocity;
}

There’s a little looseness with the checks, and because there are some cases were shows show up wrong because of the ids (ex: show 311 and 3112 would both be positive for a check on 311), we have to double up on the checks to make sure that the show is really the same.

Top ↑

What’s Next? What’s Next?

There are still some places I could adjust this. Like if I use more filters I can make the show stars worth ‘more’ than the genres and so on. And right now, due to the way most Anime are based on Manga (and thus get flagged as “Literary Inspired”), anything based on Sherlock Holmes ends up with a lot of recommended Anime.

Still, this gives me a way more flexible way to list what’s similar.