My previous post on writing a simple related posts plugin was a popular one, and one reader wanted to take it further for a website he was working on. Well it was painfully obvious that my example was inadequate for doing any sort of fun stuff so I started adding bits and pieces to create a more robust widget. If you haven’t already, you might want to read the first post to get caught up.

This isn’t a true “plugin” in the sense you can activate it in WordPress, but more of a widget you can plug into your theme. This widget is going to grab posts based on tags and return specific info from the matching posts. This will be a good chance to see some of the guts of WordPress being used and how you might use them for your own projects.

Start Your Hacking Engines

We’ll first set up our widget.

<?php
//Start the related posts widget
function widget_related_posts() {
	//code goes here
} //End widget and register the widget for use in WordPress
       if ( function_exists('register_sidebar_widget') )
       register_sidebar_widget(__('Related Posts'), 'widget_related_posts');
?>

Ok, nothing major, got our widget ready, I’ll be leaving this out of the code for the time being but everything I talk about below is contained where is says “code goes here”.

PHP Is Your Friend

Not really. Ok first things first, we need to get the tags for the current post. This is easy, we’ll use the function wp_get_post_tags(). Also set $post to global(we’ll use $wpdb later so I’ll set that to global too). In order to get the current post’s tags use $post->ID, if you want to make sure you are getting the tags use print_r(). The example below should make this all clear.

<?php
               global $wpdb, $post;
               $tags = wp_get_post_tags($post->ID);
               //print_r ($tags);

Now we have a variable named $tags, which has…tags, genius I know. Now we need to do something with them.

<?php
global $wpdb, $post;
               $tags = wp_get_post_tags($post->ID);
               //print_r ($tags);

               $taglist = "'" . str_replace("'",'',str_replace('"','',urldecode($tags[0]->term_id))) ."'";

               $tagcount = count($tags);
                       if ($tagcount > 1) {
                       for ($i = 1; $i <= $tagcount; $i++) {
                               $taglist = $taglist . ", '" . str_replace("'",'',str_replace('"','',urldecode($tags[$i]->term_id))) . "'";
                       }
                       }

What happens here is we get some info from the tags for use in a query(more on that in a sec). The first $taglist is used if only one tag is present on the post. The second $taglist checks if there is more than one tag, then loops through each one, preparing them for whats next.

<?php
global $wpdb, $post;
               $tags = wp_get_post_tags($post->ID);
               //print_r ($tags);

               $taglist = "'" . str_replace("'",'',str_replace('"','',urldecode($tags[0]->term_id))) ."'";

               $tagcount = count($tags);
                       if ($tagcount > 1) {
                       for ($i = 1; $i <= $tagcount; $i++) {
                               $taglist = $taglist . ", '" . str_replace("'",'',str_replace('"','',urldecode($tags[$i]->term_id))) . "'";
                       }
                       }

               $limit = "LIMIT 10";
               $now = current_time('mysql',1);
               $q = "SELECT DISTINCT pm.meta_id, pm.meta_key, pm.meta_value,
					p.ID, p.post_title, p.post_date, p.comment_count, count(t_r.object_id) as cnt FROM $wpdb->term_taxonomy t_t,
					$wpdb->term_relationships t_r, $wpdb->posts p, $wpdb->postmeta pm
					WHERE t_t.taxonomy='post_tag' AND t_t.term_taxonomy_id = t_r.term_taxonomy_id
					AND t_r.object_id  = p.ID
					AND (t_t.term_id IN ($taglist)) AND p.ID != $post->ID AND p.post_status = 'publish'
					AND p.post_date_gmt < '$now' GROUP BY t_r.object_id
					ORDER BY cnt DESC, p.post_date_gmt DESC $limit";

				$related_posts = $wpdb->get_results($q);
               // print_r ($related_posts);

There is a lot going on here. What I’ve added is a database query($q). This query is going to fetch the posts we want, and what we want from those posts. I’ll explain in short what is going on, but the WordPress codex would be a good place to learn more about this.

<?php
$q = "SELECT DISTINCT pm.meta_id, pm.meta_key, pm.meta_value,
					p.ID, p.post_title, p.post_date, p.comment_count, count(t_r.object_id) as cnt FROM $wpdb->term_taxonomy t_t,
					$wpdb->term_relationships t_r, $wpdb->posts p, $wpdb->postmeta pm

This line selects distinct posts and what we want from those posts. The pm.* are getting the custom field information. The p.* are getting post info(ID, Title, Date, Comment Count). The “FROM” part is telling the query which databases to look for the information in.

<?php
WHERE t_t.taxonomy='post_tag' AND t_t.term_taxonomy_id = t_r.term_taxonomy_id
					AND t_r.object_id  = p.ID
					AND (t_t.term_id IN ($taglist)) AND p.ID != $post->ID AND p.post_status = 'publish'
					AND p.post_date_gmt < '$now'

The WHERE section is telling the database you only want posts that match "where this criteria is met". In this case we only want posts that match the tags associated with the current post. You could read this statement like so: Get posts that have tags matching those in $taglist but don't get the current post, make sure the post is published before the very second(ie don't get posts to be published in the future).

<?php
GROUP BY t_r.object_id
					ORDER BY cnt DESC, p.post_date_gmt DESC $limit";

				$related_posts = $wpdb->get_results($q);

We then group the posts by their ID in a descending order by date, but we limit the amount of posts to $limit(which is equal to 10 in this case). Then we run the query through the WordPress database, and set it to a variable called $related_posts.

Thats it, almost. Now we have an array with posts and their info and we are ready to print it out. But first a quick recap. We have gotten the tags for the current post, set them up for a database query, ran them through the database and got all the posts we want(and their specific info), then stuck them in an array. And it all looks like this.

<?php
global $wpdb, $post;
               $tags = wp_get_post_tags($post->ID);
               //print_r ($tags);

               $taglist = "'" . str_replace("'",'',str_replace('"','',urldecode($tags[0]->term_id))) ."'";

               $tagcount = count($tags);
                       if ($tagcount > 1) {
                       for ($i = 1; $i <= $tagcount; $i++) {
                               $taglist = $taglist . ", '" . str_replace("'",'',str_replace('"','',urldecode($tags[$i]->term_id))) . "'";
                       }
                       }

               $limit = "LIMIT 10";
               $now = current_time('mysql',1);
               $q = "SELECT DISTINCT pm.meta_id, pm.meta_key, pm.meta_value,
					p.ID, p.post_title, p.post_date, p.comment_count, count(t_r.object_id) as cnt FROM $wpdb->term_taxonomy t_t,
					$wpdb->term_relationships t_r, $wpdb->posts p, $wpdb->postmeta pm
					WHERE t_t.taxonomy='post_tag' AND t_t.term_taxonomy_id = t_r.term_taxonomy_id
					AND t_r.object_id  = p.ID
					AND (t_t.term_id IN ($taglist)) AND p.ID != $post->ID AND p.post_status = 'publish'
					AND p.post_date_gmt < '$now' GROUP BY t_r.object_id
					ORDER BY cnt DESC, p.post_date_gmt DESC $limit";

				$related_posts = $wpdb->get_results($q);
               // print_r ($related_posts);

Almost Done, Outputting

Ok now we have our posts sitting in an array and we need to output all the info. I'm going to hard code in all the html for this example, but this is not always the best thing to do(Check out the widgets API for other ways). I'll start at the top:

<?php
//Start the related posts widget
function widget_related_posts() { ?>
	<div class="widget related_posts">
	       <h3>Related Posts</h3>
<?php

	global $wpdb, $post; ...

All I did there was add in a div (you could also use a unordered list here) and some class names to hook CSS into, also I added the title tag.

<?php
$related_posts = $wpdb->get_results($q);
	                //print_r ($related_posts); ?>

		<ul>
			<?php
			foreach ($related_posts as $related_post):
			//print_r ($related_post); ?>

This is the next step, after running our query take the array and do a "foreach" loop on it. Now we can output all the data we want.

<?php
<li><a href="<?php echo get_permalink($related_post->ID); ?>">
					<?php echo $related_post->post_title ?></a></li>

				    	<?php //get thumbnail (custom field)
				        $image = get_post_meta($related_post->ID, 'image', true);
						if ( $image != '') {?>
				         	<a href="<?php echo get_permalink($related_post->ID); ?>" rel="bookmark"
								title="Permanent Link to <?php echo $related_post->post_title ?>">
				      		<img src="<?php echo $image; ?>" alt="<?php echo $related_post->post_title ?>" /></a>

				    	<?php } endforeach; ?>

		</ul>
	</div>

This is all the really important stuff here. I didn't use regular wordpress template tags here, I pulled the info straight out of the array. This example gives you a list with the title of the post and an image assigned to the custom field key "image". It also checks to see if anything is assigned to "image" so you don't get unexpected results.

The Whole Thing

Obviously you wouldn't want to use this for just related posts as there are plenty of plug-ins that can do that, but if you want specific results, the plug-ins are usually too general. The reader was going to use it to display related dvd's on posts, which is a perfect example of why you would want to build your own widget.

Here is the final code:

<?php
//Start the related posts widget
function widget_related_posts() { ?>
	<div class="widget related_posts">
	       <h3>Related Posts</h3>
<?php

	global $wpdb, $post;
	               $tags = wp_get_post_tags($post->ID);
	               //print_r ($tags);

	               $taglist = "'" . str_replace("'",'',str_replace('"','',urldecode($tags[0]->term_id))) ."'";

	               $tagcount = count($tags);
	                       if ($tagcount > 1) {
	                       for ($i = 1; $i <= $tagcount; $i++) {
	                               $taglist = $taglist . ", '" . str_replace("'",'',str_replace('"','',urldecode($tags[$i]->term_id))) . "'";
	                       }
	                       }

	               $limit = "LIMIT 10";
	               $now = current_time('mysql',1);
	               $q = "SELECT DISTINCT pm.meta_id, pm.meta_key, pm.meta_value,
						p.ID, p.post_title, p.post_date, p.comment_count, count(t_r.object_id) as cnt FROM $wpdb->term_taxonomy t_t,
						$wpdb->term_relationships t_r, $wpdb->posts p, $wpdb->postmeta pm
						WHERE t_t.taxonomy='post_tag' AND t_t.term_taxonomy_id = t_r.term_taxonomy_id
						AND t_r.object_id  = p.ID
						AND (t_t.term_id IN ($taglist)) AND p.ID != $post->ID AND p.post_status = 'publish'
						AND p.post_date_gmt < '$now' GROUP BY t_r.object_id
						ORDER BY cnt DESC, p.post_date_gmt DESC $limit";
					//echo $q;
					$related_posts = $wpdb->get_results($q);
	                //print_r ($related_posts); ?>

		<ul>
			<?php
			foreach ($related_posts as $related_post):
			//print_r ($related_post); ?>

				<li><a href="<?php echo get_permalink($related_post->ID); ?>">
					<?php echo $related_post->post_title ?></a></li>

				    	<?php //get thumbnail (custom field)
				        $image = get_post_meta($related_post->ID, 'image', true);
						if ( $image != '') {?>
				         	<a href="<?php echo get_permalink($related_post->ID); ?>" rel="bookmark"
								title="Permanent Link to <?php echo $related_post->post_title ?>">
				      		<img src="<?php echo $image; ?>" alt="<?php echo $related_post->post_title ?>" /></a>

				    	<?php } endforeach; ?>

		</ul>
	</div>
<?php
} //End widget and register the widget for use in WordPress
       if ( function_exists('register_sidebar_widget') )
       register_sidebar_widget(__('Related Posts'), 'widget_related_posts');

A lot of this code is hacked up and stripped down from current plug-ins which I highly recommend looking into the code of if you are curious about WordPress and it's inner workings. Seeing how other more advanced people code will open you up to new ideas.

Update

One of my readers Martin read the first article about a related posts widget and wanted to take it further, the result of that conversation was this article. Once again Martin decided to take things even further and here are the results.

This example makes sure the widget title will not be shown if there are no related posts and instead replaces it with a google ad. This is a great use of real estate on a website.

<?php function widget_related_dvds() {
        global $wpdb, $post;
        $title = get_the_title($post->ID);

        $taglist = "'" .

        str_replace("'",'',str_replace('"','',urldecode($tags[0]->term_id)))
        ."'";

        $tagcount = count($tags);
        if ($tagcount > 1) {
                for ($i = 1; $i <= $tagcount; $i++) {
                        $taglist = $taglist . ", '" .
                        str_replace("'",'',str_replace('"','',urldecode($tags[$i]->term_id)))
                        . "'";
                }
        }

        $limit = "LIMIT 10";
        $now = current_time('mysql',1);
        $q = "SELECT DISTINCT pm.meta_id, pm.meta_key, pm.meta_value, p.ID, p.post_title, p.post_date, p.comment_count, count(t_r.object_id) as cnt FROM $wpdb->term_taxonomy t_t, $wpdb->term_relationships t_r, $wpdb->posts p, $wpdb->postmeta pm WHERE t_t.taxonomy='post_tag' AND t_t.term_taxonomy_id = t_r.term_taxonomy_id AND t_r.object_id  = p.ID AND (t_t.term_id IN ($taglist)) AND p.ID != $post->ID AND p.post_status = 'publish' AND p.post_date_gmt < '$now' GROUP BY t_r.object_id ORDER BY cnt DESC, p.post_date_gmt DESC $limit";

        $related_posts = $wpdb->get_results($q);

        if (count($related_posts) > 0) {
                print <h2 id="availability">DVD</h2>';
        }
        else {
                print '<div class="adver">';
                include(TEMPLATEPATH . '/google-ad-120x240.php');
                print '</div><!-- end adhor -->';
        }
?>

This example matches posts based on the Title and a certain category (in this case the DVD category). Note this is just the query portion of the script.

<?php
global $wpdb, $post;
        $title = get_the_title($post->ID);
        $limit = "LIMIT 10";
        $now = current_time('mysql',1);
        $q = "SELECT DISTINCT
                p.ID,
                p.post_title,
                p.post_category,
                p.post_date,
                p.comment_count,
                count(t_r.object_id) as cnt
                FROM
                $wpdb->term_relationships t_r,
                $wpdb->posts p,
                $wpdb->postmeta pm
                WHERE p.post_title = '$title'
                AND p.post_category = 'DVD'
                AND t_r.object_id  = p.ID
                AND p.ID != $post->ID
                AND p.post_status = 'publish'
                AND p.post_date_gmt < '$now'
                GROUP BY t_r.object_id
                ORDER BY cnt DESC, p.post_date_gmt DESC $limit";

        $related_posts = $wpdb->get_results($q);

Still not enough? It wasn't for Martin. Add this to the script and match posts based on tags and title!

<?php
$title = get_the_title($post->ID);
$related_posts = query_posts("category_name=DVD&tag=$title+$title");

There you go, three examples of this simple example made into something truly useful. If you have used this script or have a better way to do things, leave a comment!