Hacking the WordPress Popular Posts Plugin for a Raw Data Array

A popular posts list can be a great way to encourage readers to explore more articles on your site. We see them all over the web in the form of trending articles, and if you spend as much time developing with WordPress as I do, you certainly would have come across this requirement before. As with most things in WordPress, there are plenty of plugins out there that can get you up and running fairly easily, all with varying levels of flexibility and functionality, but finding plugins that offer the flexibility a developer needs can often be a challenge.

As a developer who spends a lot of time building custom WordPress themes, I’m not particularly insterested in plugins that lack flexibility. It is my belief that a solid plugin should have both a stable and flexible API layer, as well as an out-of-the-box layer that builds upon the plugin’s own API. To me, this seems like a sound pattern for building flexible WordPress plugins that suit both ends of the user scale, as it provides a ready solution for non-tech users, whilst also allowing developers to leverage the API for any custom requirements.

Unfortunately, this is a bit of a utopian concept in the WordPress landscape, so we often find ourselves in a position where we either need to pull a few tricks, build something from scratch, or simply make do with what is available.

I recently implemented the WordPress Popular Posts plugin into a site for the first time, and although I found the plugin to be quite flexible from a users point of view, it lacked the mechanism I needed for accessing the raw data. After some reading, I found that I would most likely have to go through a cumbersome process of filtering the output of the plugin to get the layout I needed. My days of extensive hooking and filtering to modify markup have taught me the value and power of using raw data, so I set out to find a way around the problem.

Why not just pull straight from the database?

I had considered this, as it seems like a reasonable possibility. But, the more I thought about it, the less appropiate this solution was. The plugin actually maintains quite a lot of data along with its own table structure, so I would only be writing queries for a potentially unstable database that I don’t control. This scenario comes with a higher probability of breakages down the track, so I needed to find a better way. Ideally, a way that I could leverage existing plugin API methods to ensure maximum future compatibility…

Hacking on the template tags turned out to be the way to go

The plugin does have some template tags (functions) available, which turned out to be part of the answer, but the initial problem with the functions is that they are actually designed to drop markup into a template. What I did find interesting, however, was that the wpp_get_mostpopular() function actually builds a shortcode which dumps the output, and the shortcode that it builds just happens to have an available filter.

Now, I realise I said I wasn’t interested in going crazy with filters, but the thing that caught my eye was the fact that the first argument passed to the filter was the raw data I wanted – I just needed a way to catch that data so that I could repurpose it for my own needs.

The solution was to create a class which I could instantiate whenever I needed a data set of popular posts. To work, the class would need the following;

  1. An invocation of the wpp_get_mostpopular() function that doesn’t actually produce any markup;
  2. A hooked filter to catch the output of the template tag and capture the data in a raw state;
  3. A method that would return the data we need;
  4. The ability to take arguments that would allow us to alter that data set.

Having this meant I could instantiate a list of popular posts – as many times as I required and with varying options on each instance – and use the post data to render any type of view that I needed. This gave me full control of the output.

The Code

Note:

This code was written using version 3.2.1 of the WordPress Popular Posts plugin, running on WordPress version 4.1. I cannot guarantee this will work on different versions.

You are welcome to use this code under the terms outlined in the Creative Commons Attribution 4.0 license.

<?php

/**
 * Class Popular_Posts_Data
 *
 * A class for retrieving an array of popular posts post data. This class is designed for use with the WordPress Popular
 * Posts plugin (@see https://wordpress.org/plugins/wordpress-popular-posts/) and will not work without it.
 *
 * @author 	Phil Smart
 * @license https://creativecommons.org/licenses/by/4.0/legalcode Creative Commons Attribution License
 */
class Popular_Posts_Data
{
    /** @var array $items Property for storing the final data array */
    protected $items = array();

    /**
     * Constructor. This accepts either an integer or an array of query args suitable for the wpp_get_mostpopular()
     * function. If an int is passed, it is treated as the number of posts to get. If an array is passed, it is
     * treated as a plugin-specific query array.
     *
     * @param int|array $args
     */
    function __construct( $args = 10 )
    {
        // Make sure the function is available
        if ( ! function_exists( 'wpp_get_mostpopular' ))
            return;

        // Catch the output of the function to prevent it from displaying and capture the data via the filter
        add_filter( 'wpp_custom_html', array( $this, '_get_posts_data' ), 10, 2 );

        // Ensure our arguments are of a suitable format
        $args = $this->normalise_args( $args );

        // Invoke the API method using our arguments array, and catch the output in the output buffer
        ob_start();
        wpp_get_mostpopular( $args );
        ob_get_clean();

        // Remove the filter so we don't affect other instances of the shortcode
        remove_filter( 'wpp_custom_html', array( $this, '_get_posts_data' ), 10, 2 );
    }

    /**
     * Normalises args for the wpp_get_mostpopular() function. This ensures we pass the function a correctly formatted
     * args array, but also allows us to define some default arguments specific to our needs.
     *
     * @param int|array $args
     *
     * @return array
     */
    protected function normalise_args( $args )
    {
        // If an integer is passed, treat it as a limit
        if (is_int( $args ))
            $args = array( 'limit' => $args );

        // Allows passing of an array of categories by reformatting into a string
        if (isset( $args['cat'] ) and is_array( $args['cat'] ))
            $args['cat'] = implode( ',', $args['cat'] );

        // Merge parameters with defaults. Set whatever defaults you like here.
        return wp_parse_args( $args, array(
            'range'     => 'weekly',
            'freshness' => 1,
            'orderby'   => 'views',
            'limit'     => 10,
            'post_type' => 'post'
        ) );
    }

    /**
     * The hooked filter that intercepts the shortcode output and receives a list of posts.
     *
     * @param $data
     * @param $instance
     */
    public function _get_posts_data( $data, $instance )
    {
        $this->items = (array) $data;
    }

    /**
     * Public accessor for posts array.
     *
     * @return array
     */
    public function get_posts()
    {
        return $this->items;
    }
}

Usage

<?php

// Simply get the five most popular posts
$query = new Popular_Posts_Data(5);
$array = $query->get_posts();

// More advanced query
$query = new Popular_Posts_Data(
      array(
            'range' => 'monthly',
            'limit' => '15',
            'cat'   => '234, 12, 54'
      ) );
$array = $query->get_posts();

Conclusion

Using this technique, we are able to add a new layer of flexibility to the WordPress Popular Posts plugin. Be mindful, however, that plugins change and those changes are not always fully backwards compatible. By writing your own code that relies on third party plugins, you will most likely need to maintain and adapt your code at some point. My decision to leverage existing inputs/outputs built into the plugin was purely to mitigate the potential for ongoing maintenance, but I don’t expect this to last forever.

Need a website? Let's talk.

From website design & SEO through to custom WordPress plugin development. I transform ideas into dynamic, engaging, and high-performing solutions.
Subscribe to get the latest insights & updates in the world of web and how it impacts your business and website.
© 2024 Phil Kurth  |  All rights reserved.