Enabling Popularity Contest for WordPress networkwide use

Alex King’s Popularity Contest is a pretty cool way to collect data about which posts on a WordPress site the most popular. The data collected is more sophisticated and customizable than simple analytics, because it distinguishes between page views and things like trackbacks, comments, and other kinds of hits. The plugin supports WordPress Multisite in the sense that it’s possible to activate network-wide; when you do so, the plugin keeps site-specific popularity stats. But what if you want popularity rankings across your entire network?

I recently modified Popularity Contest to do just that. The idea is simple: in order to keep network-wide stats, we need a network-wide table (instead of the default site-specific tables). How do you keep information about all networkwide posts on a single blog? I used Donncha O Caoimh’s Sitewide Tags as a bridge. All posts across the network are copied to the tags blog, and popularity data is indexed on the tags blog.

To make this work, several things are needed. I can’t just give you the files because I’ve altered them in other, irrelevant ways, but I will walk you through the process of setting it up. Also, keep in mind that we’ll be modifying the plugin code for both Popularity Contest and Sitewide Tags, modifications you’ll have to make each time you upgrade the plugins. Make sure you back up your work.

  1. Install Sitewide Tags which can be downloaded from its website. You have to activate a few things in order to turn it on – read the readme carefully. Be sure to take note of the tags blog id number, as we’ll need that in a later step.
  2. In sitewide-tags.php, look for the function sitewide_tags_post(). Near the end of the function is a line that says restore_current_blog();. Immediately after that line, enter the following code:
    [code language=”php”]update_post_meta( $post->ID, ‘tags_post_id’, $p );[/code]
    That line makes sure that every time a post is aggregated on the tags blog, the original post gets a piece of metadata noting the post id of the corresponding tags blog post. We’ll use that information in a later step.
  3. Download Popularity Contest from its website. I don’t recommend that you activate it yet, especially not networkwide, because it will create a lot of tables that you don’t really need.
  4. The next few steps will require mading some modifications in the main Popularity Contest plugin file, popularity-contest.php. The first modification is to change all references to $wpdb->posts (which, when activated networkwide, will refer to the posts table for the individual blogs) and change them to point to the tags blog post table. A search and replace that replaces $wpdb->posts with wp_posts (or wp_x_posts, if your tags blog is not site number 1 but is instead site x.
  5. The next modification involves the function akpc_init(), near the end of the plugin file. That’s where the table names for the Popularity Contest custom tables are found. We need to make sure that they point to the tags blog. Replace the existing function with this:
    [code language=”php”]
    function akpc_init() {
    global $wpdb, $akpc;

    $wpdb->ak_popularity = ‘wp_ak_popularity’;
    $wpdb->ak_popularity_options = ‘wp_ak_popularity_options’;

    $akpc = new ak_popularity_contest;
    $akpc->get_settings();
    }
    [/code]
    If your tags blog is something other than blog 1, you could change these table names to match (e.g. wp_15_ak_popularity) but it isn’t really necessary.

  6. Now we have to make Popularity Contest aware of the identity relationships between the tags posts and the original posts. Two snippets of code should do it in most places. First, find the function record_feedback, which starts around line 700. Right before the switch($type) line, insert the following:
    [code language=”php”]
    if ( $tags_post_id = get_post_meta( $comment_post_ID, ‘tags_post_id’, true ) )
    $comment_post_ID = $tags_post_id;
    [/code]
    Next, find the function akpc_api_record_view(), which starts around 2550. Right after array_unique($ids); (around line 2555), insert the following:
    [code language=”php”]
    $tags_ids = array();
    foreach ( $ids as $id ) {
    $tags_ids[] = get_post_meta( $id, ‘tags_post_id’, true );
    }
    $ids = $tags_ids;
    [/code]
    These two modifications make sure that Popularity Contest knows which post on the tags blog corresponds to the post being visited/commented on on the child blogs.
  7. At this point, you can activate the popularity plugin networkwide. Here’s what happens, very roughly:
    • The plugin creates the necessarily popularity tables – just one set for the whole installation.
    • When you publish a new post on any site, it gets copied to the tags blog. Our modification from step 2 makes sure that the copied post ID (let’s say 36) is saved to the original post.
    • When someone visits the original blog post, Popularity Contest fires (because it’s been activated network wide). Our modifications in steps 4 and 5 make sure that the plugin knows to record the activity to the tags blog index, and step 6 make sure that the plugin know which post the activity belongs to.
  8. You’ll need another modification to get the data out, since you’ll want to display it on your site somewhere. The default function for this is called show_top_ranked(). We need to modify so that it gets the requested data from the right place. Replace the stock function with this one:[code language=”php”]
    function show_top_ranked($limit, $before, $after) {
    switch_to_blog( 1 );
    if ($posts=$this->get_top_ranked_posts($limit)) {
    foreach ($posts as $post) {
    $ud = get_userdata( $post->post_author );
    print(
    $before. get_thumbnail( $post->post_author, 36 ) .’ID).'”>’
    .$post->post_title.’

    ‘. $ud->display_name . ‘‘ . $after
    );
    }
    }
    else {
    print($before.'(none)’.$after);
    }
    restore_current_blog();
    }
    [/code]
    Make sure you change the number in the switch_to_blog() call to the id of your tags blog.

I think I’ve remembered everything. Good luck!

Bonus!

For my project, I was moving from a single WordPress site to a multisite situation. The popularity plugin had been running on both setups for a while, so the data was totally messed up and needed to be combined (which meant finding the corresponding post data and adding it together – yeesh!). Here’s the script I used – be careful with it, and keep in mind that it was designed for a *very* specific use. Do not use this code if you don’t understand exactly what every line does!

[code language=”php”]
global $wpdb;

$query = “SELECT * FROM {$wpdb->blogs} WHERE site_id = ‘{$wpdb->siteid}’ “;
$blog_list = $wpdb->get_results( $query, ARRAY_A );

foreach( $blog_list as $blog ) {
//print_r($blog); continue;
if ( $blog[‘blog_id’] == 1 ) continue;
//if ( $blog[‘blog_id’] != 83 ) continue;

$tn = ‘wp_’ . $blog[‘blog_id’] . ‘_posts’;
$tnmeta = ‘wp_’ . $blog[‘blog_id’] . ‘_postmeta’;

$query = “SELECT ID FROM {$tn} WHERE post_type = ‘post’ AND post_status = ‘publish’ “;
$posts = $wpdb->get_results( $query, ARRAY_A );

foreach( $posts as $post ) {
$id = $post[‘ID’];

$query = “SELECT meta_value FROM {$tnmeta} WHERE post_id = ‘{$id}’ AND meta_key = ‘tags_post_id’ “;
$tags_post_id = $wpdb->get_results( $query, ARRAY_A );
$tpid = $tags_post_id[0][‘meta_value’];

$query = “SELECT * FROM wp_ak_popularity WHERE post_id = ‘{$id}'”;
$old_data = $wpdb->get_results( $query, ARRAY_A );
$old_data = $old_data[0];

$query = “SELECT * FROM wp_ak_popularity WHERE post_id = ‘{$tpid}'”;
$new_data = $wpdb->get_results( $query, ARRAY_A );
$new_data = $new_data[0];

if ( $old_data && $new_data ) {
$combined_data = array();

$combined_data[‘post_id’] = $new_data[‘post_id’];
$combined_data[‘last_modified’] = $new_data[‘last_modified’];

foreach( $old_data as $key => $d ) {
if ( $key == ‘post_id’ || $key == ‘last_modified’ )
continue;

$combined_data[$key] = (int)$d + (int)$new_data[$key];
}
}

$query = ‘UPDATE wp_ak_popularity SET ‘;
foreach( $combined_data as $key => $cd ) {
if ( $key == ‘post_id’ )
continue;
$query .= “{$key} = ‘{$cd}’, “;
}

$query = substr_replace( $query, ”, -2 );
$query .= ‘ ‘;

$query .= “WHERE post_id = ‘{$combined_data[‘post_id’]}'”;
$wpdb->query( $query );
print_r( $old_data ); echo “
“; print_r($new_data); echo “
“; print_r($combined_data); echo “
“; echo $query; echo “

“;

}

echo “

";
//		print_r($posts);
echo "

“;
}
[/code]

7 thoughts on “Enabling Popularity Contest for WordPress networkwide use

  1. Simon

    Very nice tut Boone, thanks a lot for this!

    However there are a few questions I have regarding this setup.

    I fail to grasp the concept behing the sitewide tags plugin. All it does is copy over blog posts into a new blog right? Does that create duplicate content? Or what *exactly* is the new blog for?

    Then the popularity contest plugin AND the sitewide tags plugin aren’t they both kind of resource heavy? And tied together maybe even more?

    I would really like to get something like this running though!

    Reply
  2. Boone Gorges Post author

    Hi Simon,

    The original purpose of the sitewide tags plugin is to keep track of popular tags/categories across an entire network of WP sites. WordPress keeps taxonomy information in site-specific tables, a fact which Sitewide Tags exploits by copying everything on the network into a single site and generating sitewide tag clouds, etc from the aggregated blog.

    In the case of Popularity Contest, I’m doing something similar. If you don’t have a centralized index of all the content on your network, then there’s no way to have centralized data about that content. Popularity Contest can’t tell the difference between post_id = 10 on boone.awesome.com and post_id = 10 on simon.awesome.com unless you go through the intermediary of tags.awesome.com, where they have different post ids.

    As for performance: The performance hit for sitewide tags should be negligible. It only kicks in when you publish or save a post, which, unless you have a *very* active blogging network, won’t be very often. Popularity Contest, on the other hand, records data on every site *visit*. This could potentially be pretty resource-intensive. You probably want to do some tests before launching it on a big site, especially on shared hosting.

    Reply
  3. shawn

    I noticed that the sitewide tags plugin only seems to be pulling in posts and not custom post-types from all the sites.

    Scenario:
    All of my network sites have a custom post-type ‘testimonials’ built into their theme functions file.

    As the ‘root’ site also has the same custom post-type and needed templates, is it possible to pull in all the testimonials from the network to display on the root site using this plugin?

    –All the sites actually share 7 different post-types, so in the end would need to replicate the functionality to pull them all into the primary site.

    Reply
  4. Boone Gorges Post author

    shawn – There’s a filter in sitewide-tags.php that allows you to add additional post types. The filter is called sitewide_tags_allowed_post_types. Thus, if you want to add the post type ‘testimonials’, the following should work (in another plugin file within mu-plugins):

    function add_testimonial_type( $allowed_post_types ) {
    $allowed_post_types['testimonials'] = true;
    return $allowed_post_types;
    }
    add_filter( 'sitewide_tags_allowed_post_types', 'add_testimonial_type' );

    To add more post types, just add additional lines that say
    $allowed_post_types['my_post_type'] = true;
    where ‘my_post_type’ is your custom post type name.

    Reply
  5. Paul

    Thanks Boone. re: #4.
    I’m trying to add ‘product’ post type and using the filter code I created a new mu-plugin file called sitewide_tags_allowed_post_types.php

    function add_product_type( $allowed_post_types ) {
    $allowed_post_types[‘product’] = true;
    return $allowed_post_types;
    }
    add_filter( ‘sitewide_tags_allowed_post_types’, ‘add_product_type’ );
    ?>

    any ideas? cheers

    didnt work for me though 🙁

    I have posts and pages enabled in SuperAdmin Options too.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *