WordPress plug-in hacks

I’ve been meaning to post this ever since I transitioned to the new blog.

I’ve already explained in another post the frustrations I have with regards to the approach of WordPress plugins 1 How “snobbishly self-contained they tend to perform, etc.

This post however is to assert that despite all these “limitations” there is one developer that seems to have found an awesome workaround – and if people just get on board with his plugin architecture, most of the “silly issues” plaguing WP plugins could be solved.

So yeah, I guess this post is more of an evangelical one to [hopefully] give more exposure to Mr. Rob Marsh SJ and his extremely versatile Post-Plugin Library.

Sample Problem

Just to practically demonstrate the “issues” I have, lets take any individual entry page in my site; like what you’re reading right now. 2 Assuming you’re not reading this through an RSS reader At the bottom of this page, we have the “Probably Related” section.

For any advanced user, it goes without saying that that’s the work of a plugin. And plugins tend to hit-or miss as far as crafting the exact output code goes.

My blog, unfortunately, already has a specific “format” as far as what data and visual elements are exposed; this was since I first made it in MovableType and that layout has been set in stone for “user interface efficiency.” These plugins, taken individually, do not support that level of detail on their own.

For example, there are a lot of “Related Posts” plugins available… but my particular related posts entries need to have a primary category icon. WordPress supports multi-categories, but doesn’t have any provision for calling the primary category… so you need another plugin to do that… which is obviously not included (why should it be?) in the “Related Posts” plugins you will find floating around. Same goes for the need of a word-count… or a better way of displaying excerpts.

All of which by the way are available through separate plugins. But like I said in the beginning, unlike MovableType, these plugins tend to only look after themselves… and not take the time to “integrate” with the system as a whole.

So If say I did a simple call to that excerpt plugin I use within the “Related Posts” (whether hacked the code, or if the plugin supports inline function calls 3 Which I’ll get to in a minute ) – all the excerpts of those “posts” will be a single excerpt of the main post you’re reading – because it will not recognize that you’re calling it from within another plugin which is essentially doing a new “the_loop.”

Rob Marsh’s Post-Plugin Library

Rob Marsh, a Jesuit 4 I just had to mention that since I studied in a Loyola school as well approaches plugin development very practically with his Post-Plugin library

His general approach to plugins allows the user to practically have complete control over the output formatting – by using template tags within the plugin. Plugins using their own template tags is not new, however this in conjunction with the Post-Plugin Library makes even the tags themselves extendable.

For example, here’s the “output” code I “set” on my “Probably Related” entries. 5 And yes, those ARE table elements, I don’t give a shit if they’re deprecated

<tr>
    <td valign="top" class="nRecentItemSide{alternate}">
        <div class="nItemTime"><a href="{url}" title="Read "{title}"">{date:Y.m.d} {time}</a></div>
        <div class="nItemTopic"><a href="http://nargalzius.com/blog/archives/category/{topcat}" title="Primary Category: {topcat}">{topcat}</a></div>
        <div class="nItemWords">{wordcount} words</div>
        <div class="nItemComment"><a href="{url}#comments" title="view/post comments">{commentcount:No comments:comment:comments}</a></div>
    </td>
    <td valign="top" class="nRecentItemMain{alternate}">
        <div class="nRecentItemTitle"><a href="{url}" title="Read "{title}"">{title}</a></div>
        <div class="nRecentItemContent">
            {advexcerpt}
            <div class="read_more"><a href="{url}" title="View individual entry" class="nMainEntryButton nRead">READ</a></div>
        </div>
    </td>
    <td class="nArchivePostRows{alternate}">
        <a href="http://nargalzius.com/blog/archives/category/{topcat}" title="Primary Category: {topcat}"><img src="http://www.nargalzius.com/f/i/blog/icon_topic_{topcat}.jpg" alt="" /></a>
    </td>
</tr>

The template tags are in the curly braces, and you can see the list of commonly available tags (thanks to the Post-Plugin Library) here. But also notice that there are tags in there that aren’t included in the documentation such as {alternate}, {topcat}, {advexcerpt}, and {wordcount} among others.

That’s because they were added by me. And that’s my whole point of evangelizing this library. Mr. Marsh designed it in such a way that you can easily add tags and functions and they’ll immediately be useable across all of his plugins. Now imagine if every developer would use the same framework… I mean they wouldn’t have to reinvent anything – it’s already made… all they have to do is use it.

And to tell you the truth, if you install any of Mr. Marsh’s plugins, and see how configurable they are, they practically cover every customizable element you can think of – so the framework itself is very well thought out.

Enough of the pimping

I think I’ve said enough, if people don’t leverage this framework for their own needs, its their loss. I on the other hand, would like to share some “additions/hacks/modifications” I made to the library which allowed me to achieve the level of detail and visual eye-candy with the plugins.

Most of the code snippets below essentially make it possible for the Post-Plugin Library to “tap into” other plug-in’s functions and use those functions for itself (and the plugins that use it).

TopCat

Add this block of code in output_tags.phpto be able to use the {topcat} tag

//////////////////////////////////////////////////////////////////////////
// INTEGRATE TOP CAT PLUGIN
// http://www.thunderguy.com/semicolon/wordpress/top-cat-wordpress-plugin
//////////////////////////////////////////////////////////////////////////

function otf_topcat($option_key, $result, $ext) {

    $top_category = '';

    if(function_exists('topcat_the_main_category')) {
        $top_category = topcat_get_the_main_category($result->ID);
    } else {
        $top_category = 'SORRY, THE {topcat} FUNCTION DOESN\'T EXIST';
    }

    return $top_category;
}

Alternating rows

This is not a plug-in per se, but I do like styling alternating classes (i.e. multiple rows having alternating colors). So this addition was just begging to be made.

Add this block of code in output_tags.phpto be able to use the {alternate} tag

//////////////////////////////////////////////////////////////////////////
// CUSTOM FUNCTION THAT SETS A VARIABLE FOR ALTERNATING ROWS
//////////////////////////////////////////////////////////////////////////

$classAlternate = 0; // REQUIRED FOR FUNCTION BELOW

function otf_alternate($option_key, $result, $ext) {
    global $classAlternate;
    $classAlternate++;
    $classDistinction = '';

    if(!($classAlternate%2)) {
        $classDistinction = 'A';
    } else {
        $classDistinction = 'B';
    }

    return $classDistinction;
}

The only problem with this is that it exposed a bug in the Plugin Library’s hierarchy of processes. I had to set all the plugins to default sorting because I ended up having unpredictable class assignments precisely because the [alternating] classes were already assigned (while it was generating the unsorted output) then jumbled up because of the other sorting options.

Technically, the “output” formatting should be applied as the very last step (literally right before it actually generates the html code. So if you set it to have some special grouping or sorting parameters… have it do those things then apply the formatting to the results.

Word Counter

Add this block of code in output_tags.php to be able to use the {wordcount} tag

//////////////////////////////////////////////////////////////////////////
// INTEGRATE WORD COUNTER PLUGIN
// http://www.murraywilliams.com/software/word-count-plugin-for-wordpress
//////////////////////////////////////////////////////////////////////////

function otf_wordcount($option_key, $result, $ext) {
    if(function_exists('mtw_string_wordcount')) {
        return mtw_string_wordcount($result->post_content);
    } else {
        return "SORRY, THE {wordcount} FUNCTION DOESN'T EXIST";
    }
}

Avanced Excerpt

Here’s a perfect example of why I love the library. There is actually an {excerpt} tag in the Post-Plugin Library. The trouble is that it sucks; it doesn’t parse Markdown code at all, etc. etc. Sufficed to say it wasn’t working for me.

What does work though is the Advanced Excerpt Plugin from Sparepencil. Thanks to the extendability of the Library, I was able to add the function and use that instead for my excerpts.

Add this block of code in output_tags.php to be able to use the {advexceprt} tag

//////////////////////////////////////////////////////////////////////////
// INTEGRATE ADVANCED EXCERPT PLUGIN (BRUTE FORCE)
// http://sparepencil.com/code/advanced-excerpt
//////////////////////////////////////////////////////////////////////////

function otf_advexcerpt($option_key, $result, $ext) {

$text = '';

    if (class_exists('AdvancedExcerpt')) {      

        // HACKED SCOPE
        $length = (!is_null($length)) ? (int) $length : get_option('AdvancedExcerpt' . '_length');
        $use_words = (!is_null($use_words)) ? (int) (bool) $use_words : get_option('AdvancedExcerpt' . '_use_words');
        $ellipsis = (!is_null($ellipsis)) ? $ellipsis : get_option('AdvancedExcerpt' . '_ellipsis');

        $allowed_tags = (is_array($allowed_tags)) ? $allowed_tags : get_option('AdvancedExcerpt' . '_allowed_tags');
        $allowed_tags = implode('><', $allowed_tags);
        $allowed_tags = '<' . $allowed_tags . '>';

        // HACK: FORCE-FEED CONTENT TO PARSE
        $text = trim($result->post_content);

        $text = apply_filters('the_content', $text);
        $text = str_replace(']]>', ']]>', $text);
        $text = strip_tags($text, $allowed_tags);

        if(1 == $use_words) {

            // Count words, not HTML tags
            if($length > count(preg_split('/[\s]+/', strip_tags($text), -1)))
                return $text;

            // Now we start counting
            $text_bits = preg_split('/([\s]+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
            $in_tag = false;
            $n_words = 0;
            $text = '';

            foreach($text_bits as $chunk) {

                // Determine whether a tag is opened (and not immediately closed) in this chunk
                if(0 < preg_match('/<[^>]*$/s', $chunk))
                    $in_tag = true;
                elseif(0 < preg_match('/>[^<]*$/s', $chunk))
                    $in_tag = false;

                // Is there a word?
                if(!$in_tag &#38;& '' != trim($chunk) && substr($chunk, -1, 1) != '>')
                    $n_words++;

                $text .= $chunk;

                if($n_words >= $length && !$in_tag)
                    break;
            }

            $text = $text . $ellipsis;

        } else {

            // Count characters, not whitespace, not those belonging to HTML tags
            if($length > strlen(strip_tags($text)))
                return $text;

            $n_chars = 0;

            for($i = 0; $n_chars < $length || $in_tag; $i++) {

                // Is the character worth counting (ie. not part of an HTML tag)
                if(substr($text, $i, 1) == '<')
                    $in_tag = true;
                elseif(substr($text, $i, 1) == '>')
                    $in_tag = false;
                elseif(!$in_tag && '' != trim(substr($text, $i, 1)))
                    $n_chars++;
            }

            $text = substr($text, 0, $i) . $ellipsis;

        }

        $text = force_balance_tags($text);

    } else {
        $text = 'SORRY, THE {advexcerpt} FUNCTION DOESN\'T EXIST';
    }

    return $text;
}

This is also a good example of how powerful the Library is; I ended up brute-forcing the addition of the Advanced Excerpt plugin, which means I practically copied over the entire function into the library, and not “tap into” the existing plugin. This is because the way the [Advanced Excerpt] plugin was done; you can’t really “tap into” it because of how it was written.

Now if the (Advanced Excerpt) plugin had an auxiliary function like get_advExcerptByID(POST_ID) – which it doesn’t, all that convoluted code above would’ve just been replaced with something simpler like:

function otf_advexcerpt($option_key, $result, $ext) {
    if(function_exists('get_advExcerptByID')) {
        return get_advExcerptByID($result->ID);
    } else {
        return "SORRY, THE {advexcerpt} FUNCTION DOESN'T EXIST";
    }
}

Same day, different year

This is a cute feature I had from the old site – as it was an avenue of constantly exposing old entries… this in turn kept those entries from getting stale (i.e. forgotten without a trace). While it was not a priority to “revive,” I have to thank Ria for inspiring me to get it to work again. I’m sharing the “hack” I did.

There are two plug-ins I know of that do this, however given the amount of information I need available for each post, hacking those proved to be complicated.

From my discussions above, we already know how using Rob Marsh’s plugin approach/architecture is as far as supplying and extending metadata – so I decided to take one of his post plugins and create a new one out of it.

I chose the simplest; which was the Random Posts Plugin. It had all the customizability as far as the output string 6 which is what you see in HTML is concerned, and given the nature of the plugin, it already had access to the database values we’ll be needing… it was just a matter of “crafting” the plug-in’s database query to suit our needs.

First thing I did was do an en masse search and replace for the keywords that identify the plugin – this is so the plugin would register as an entirely new one; thus avoiding the possibility of conflicting with his other plugin processes. I replaced all instances of Random to SameDay… hence the plugin now registers as the “SameDay” plug-in.

After testing if it was still working (at this point, it still funcitoned as a random post generator), I now proceeded to do these minor modifications:

On the execute() function in the SameDayPosts class, simply add $post along with the other global variable declarations. What this does is make the values/data in $post available to the execute function’s scope.

Next, add the following code above line that says: $sql .= "WHERE ".implode(' AND ', $where);

$curr_month = date("n", strtotime($post->post_date));
$curr_day = date("j", strtotime($post->post_date));
$where[] = "DATE_FORMAT($wpdb->posts.post_date, '%c') = {$curr_month}";
$where[] = "DATE_FORMAT($wpdb->posts.post_date, '%e') = {$curr_day}";

This adds conditions on the query while still using Mr. Marsh’s implode approach at the end (just so everything is tidy)

Then you may optionally adjust the line that says: $sql .= " ORDER BY RAND() LIMIT $limit"; By default, it randomizes the resulting posts. In my case I changed RAND() to #wpdb->posts.post_date DESC to have it sort by descending post date.

That’s all there is to it!

Notes

Notes
1 How “snobbishly self-contained they tend to perform, etc.
2 Assuming you’re not reading this through an RSS reader
3 Which I’ll get to in a minute
4 I just had to mention that since I studied in a Loyola school as well
5 And yes, those ARE table elements, I don’t give a shit if they’re deprecated
6 which is what you see in HTML

Have a say

This site uses Akismet to reduce spam. Learn how your comment data is processed.