Subscribe via

Make Any Plugin Work with WP Super Cache

I notice that a lot of people visit my blog because they want to get WP-PostViews or Popularity Contest plugin to work with a WordPress cache plugin like WP Super Cache. In this post I will show you the general technique I use to make (almost) any plugin compatible with WP Super Cache.

Understanding WP Super Cache

As you all may know, WP Super Cache when fully enabled works by saving a copy of the generated HTML file and using .htaccess to redeliver that same static file upon repeated requests. The benefit of this is obviously the time saving from the absence of PHP execution and database queries. This “feature” also has the side effect of staticizing any parts of the page that needs to be dynamic, such as:

  1. Statistics tracking: This includes things like page view counting/displaying and visitor tracking. Some plugins that do this are WP Post Views and Popularity Contest.
  2. Dynamic visitor targeting: This includes things like showing different content depending on who your visitor is. Some plugins that do this are What Would Seth Godin Do and Who Sees Ads.

Strategy

So what can we do to make a static HTML document become dynamic? The answer is Asynchronous Javascript And XML (also better known as AJAX)!

The goal here is to use client side Javascript to make HTTP requests to the server to perform any PHP execution that you need and then return the result back to Javascript client. Here is what will take place step by step:

  1. A visitor comes to your site requesting a document.
  2. .htaccess redirects them to a static HTML file with Javascript code inside of it.
  3. The visitor downloads the said HTML file
  4. The Javascript starts executing on the client side making a special HTTP request to the server again, passing any parameters necessary for the server side PHP to do its job.
  5. The server receives the special HTTP request, the PHP code executes, and the result is sent back to the client side Javascript
  6. Client side Javascript receives the result and decides what to do with it (such as display the content).

By doing this, you have effectively made a part of the static HTML file dynamic.

Implementation

There are definitely many ways to achieve this, but I will show you one way that you can hopefully take and implement anywhere you need.

Simplify the Problem

Let’s say we have a simple plugin that displays the current server timestamp and the visitor’s referring URL. The plugin is equivalent to adding this code to your functions.php file:

<?php
function omni_ts_and_ref() {
  echo time();
  echo '<br/>';
  echo $_SERVER['HTTP_REFERER'];
}
?>

You can call this function by running <?php omni_ts_and_ref(); ?> anywhere within your theme.

Now if your site was being cached, the server timestamp will not update on subsequent visits and the visitor referring URL will be wrong for everybody except the first visitor. As you can see, we have recreated this problem with the simplest plugin.

PHP

Before we do anything we have to make sure that the PHP code is able to accept the unique Javascript requests to the server.

First, we will need to create a function hook for intercepting Javascript calls like so:

<?php
function omni_request_handler() {
  if ( isset($_GET['omni_action']) && $_GET['omni_action'] == 'ts_and_ref' ) {
    omni_ts_and_ref();
    exit();
  }
}
add_action('init', 'omni_request_handler');
?>

In the code above we have made the PHP code intercept and call omni_ts_and_ref() only if the request URL contains the GET parameter “omni_action” with the value of ‘ts_and_ref’.

Next we added this request handler function to the WordPress ‘init’ action so that it gets called before the bulk of the WordPress page starts displaying.

The next important thing worth mentioning is that the call to $_SERVER['HTTP_REFERER'] in omni_ts_and_ref() will not be the correct value if the function is called via AJAX. We have to fix the function to not use $_SERVER, but the parameter that we will pass via AJAX instead.

<?php
function omni_ts_and_ref() {
  echo time();
  echo '<br/>';
  echo urldecode($_GET['omni_ref']); // we will see how AJAX is passing this over in a bit
}
?>

Here is the complete functions.php file (rename from *.txt to *.php and ignore omni_display() for now).

Now that the PHP side is set up, let’s move on to the client side Javascript.

Javascript

As we discussed before, the client side Javascript will be making HTTP requests to the server. To make this task easier, we should use the built-in AJAX methods in the jQuery library. Here is what our Javascript code should look like. You can place this anywhere in your theme or make the call to <?php omni_display() ?> (included in functions.php).

<script type='text/javascript' src='http://example.com/wp-includes/js/jquery/jquery.js?ver=1.2.6'></script>
<div id="omni_div"></div>
<script type="text/javascript">
jQuery(document).ready(function($){
  $.ajax({
    type : "GET",
    url : "index.php",
    data : { omni_action : "ts_and_ref", omni_ref : encodeURIComponent(document.referer) },
    success : function(response){
      // the server has finished executing PHP and has returned something, so display it!
      ("#omni_div").html(response);
    }
  });
});
</script>

There are a few things to notice in the code above:

  1. The AJAX call is made to index.php. It really doesn’t matter what page the call is made to as long as we have added omni_request_handler() to the WordPress “init” hook.
  2. We have passed the key “omni_action” along with the value “ts_and_ref” to get omni_request_handler() to intercept this HTTP request.
  3. We have passed in the key “omni_ref” along with the URI encoded referrer value that omni_ts_and_ref() can use to perform an echo.
  4. On successful PHP code execution, we are dynamically writing the server response into the <div id=”omni_div”></div> block using jQuery.

Drawbacks

  1. It is AJAX so it will not work for people who browse with Javascript disabled.
  2. You have to be careful what values you want to pass through from the Javascript as these values are plain text by default. Make sure you do not try to pass through passwords or anything secure across Javascript.

Last Words

This method should work on most plugins. Though it might be a little bit involved to convert an existing plugin to be WP Super Cache compliant, it is definitely not that hard if you start coding your plugin this way from the beginning (that’s how I felt with WP Greet Box). ::cough:: Plugin developers ::cough::

Is my method weak sauce? Do you have a better method? Please feel free let me know if you have any questions, comments, or suggestions about this method in the comment form below.

Save and Share
StumbleUpon
Reddit

28 Responses to “Make Any Plugin Work with WP Super Cache”

[go to last comment]
  1. Nihar

    Thank you very much for this informative post. In the past, i wanted some links to be displayed on the blog post only if the visitor was from some country. I used Maxmind's geo targeting php code. But, it was not working as it was cached by super cache. I think using this, i can force geo targeting code to execute.

  2. Thaya Kareeson

    Thanks for visiting Nihar. Let me know what you end up with. Maybe you can submit the patch to the original author when you're done.

  3. Rishi

    Haha, I'm no where near as experienced with modifying code like you are, but just as you were able to make WP-postviews compatible with SuperCache, I think having a cacheable WP-polls plugin (even more popular than postviews) would be fantastic! Is this something you'll *hopefully* work on in the near future for us newbies? :-)

  4. Ajay

    I prefer SACK as the implementation or even better a mix of JS+PHP

  5. Thaya Kareeson

    @Rishi
    Hehe… I totally understand where you're coming from. The optimization capabilities are there, but they cannot be taken advantage of if at least one plugin is not cache compatible!

    I haven't had good experiences in the past when I've modified another author's plugin to make it cacheable. Here are some scenarios that have happened:
    1. The original author won't accept the change and the change is too small to warrant me releasing another plugin.
    2. The author accepts the change and I lose credit and traffic.
    3. The author accepts the change but doesn't know how the code works so the feature becomes broken on the next release (I have to end up supporting it for no credit or traffic).

    In all cases, it is a lose-lose for me. I want to make many WordPress plugins cache compatible but I cannot do ti all myself. This is the main reason why I wrote this article. I want to encourage plugin authors to learn how to make their plugins cache compatible themselves.

    I would suggest that you contact WP-polls plugin author (Lester Chan right?) and request that they make it cache compatible and point them to this article.

  6. BillPatrianakos

    Cool. I really don't have time to implement this technique any time soon so I was wondering if Intense Debate worked with WP Super Cache without using your technique. I see youre using Intense Debate so maybe you or someone around here would know

  7. Thaya Kareeson

    @BillPatrianakos
    Intense Debate is done entirely by Javascript so it definitely works with WP Super Cache out of the box.

  8. Nihar

    What is SACK implementation that ajay has mentioned. Do you know Thaya?

  9. Ajay

    I followed the tutorial in the codex – http://codex.wordpress.org/AJAX_in_Plugins

  10. Hüseyin Berberoglu

    Thanks good article.

  11. ted rheingold

    Great stuff. I'm working on making it work for the Vote It Up pluging.

    I've worked on WP for 5 years and never used the functions.php file in a theme that I can recall. A note to anyone using this the add_action and function work best when in the functions.php file in your therem

    Thx!

  12. ted rheingold

    I'm sure this is some noob thing, (today is literally my first day working with jQuery) but for me the value fo the div would not be replaced by the function values until I changed:

    ("#omni_div").html(response);

    to

    $("#omni_div").html(response);

    Thx again.

  13. Josh Straub

    This is a fantastic guide. I have been searching for something like this ever since it became apparent that WP Super Cache comes with significant caveats (i.e. many plugins won't function with it). Thank you for writing this.

  14. ShippudenTV

    And there's a way to make the plugin Disqus compatible with WP Super Cache!?

  15. Thaya Kareeson

    DISQUS is JavaScript based so it already works with WP Super Cache. Why do you say that it doesn't work? Have you even tried it yourself?

  16. Yenny

    Hi Thaya, I have a theme that can change the view into 3 different styles (e.g. w3sh.com) but with wp-super-cache plugin, I can't make the 3 view options of the theme to work. Any idea how to solve this?

  17. Thaya Kareeson

    Hi Yenny,
    That sounds like a tough one because you will still need PHP logic to determine which theme to serve to your visitor. WP Super Cache just serves one version to your visitor regardless.

    You might be able to explore and hack the half-on mode of WP Super Cache. In this mode, PHP actually gets ran first until the correct cache file is determined, then it is served as static HTML. I haven't had too much experience with that myself though.

  18. Badal

    Does Using Wp minify will do, instead of embedding above codes???

  19. Blogington

    Thanks for this post. I have been having a few problems with WP Super and this will help me solve them.

  20. Rafael M.

    Certainly not be applicable to any type of plugin, but it would be possible to create a plugin that runs before a cache plugins (or after)?

    I am developing a plugin that basically accounts for some access to the blog. He does not write anything on the page, but could not make it run with the cache plugin (W3 Super Cache).

    Your excellent idea certainly works, but I would try to make it run before the cache plugin is called.

    I tried to use 'init' hook with a higher priority without success.

    Any suggestions or comments?

  21. Rafael M.

    After a few more hours of work, I found the solution. I believe it can be useful for other developers with similar objective.

    If you set a 'init' hook with priority '1' (for example) at your plugin, it will run perfectly with WP Super Cache as long as you check this Advanced Setting: 'Late init. Display cached files after WordPress has loaded'.

    Note that this will only make sense if the objective of your plugin is do some background action, and has no intention of writing anything on the page.

  22. Thaya Kareeson

    Nice detective work Rafael. You would also have to turn WP Super Cache in half-on mode right? If not then the htaccess rules will just redirect users to a static file and PHP is never ran.

  23. Yoav Aner

    Hi Thaya,

    Thanks for a great post. Really inspiring. So much that I decided to create a plugin that ajaxizes other plugins or wordpress functions for you automatically. Have a look at http://blog.gingerlime.com/ajaxizing for more info and hope you can try it out.

    Cheers
    Yoav

  24. Thaya Kareeson

    Not too shabby at all! Great job!

  25. John K

    While your code sample was helpful, I changed the ajax url to
    “(site-url)/wp-admin/admin-ajax.php” and substituted

    add_action( ‘wp_ajax_nopriv_my_function’, ‘my_function’);
    add_action( ‘wp_ajax_my_function, ‘my_function’);

    for add_action(‘init’, …). Any reason why you avoided using admin-ajax.php? I had to make the substitution because the combination of index.php and add_action(‘init’ was disrupting the admin–I couldn’t log out or activate inactive plugins.

  26. Stefan

    Very useful tutorial, I want to this with my tag cloud but I figure that this way google wont see my tag cloud anymore, right? What can I do in this case?

  27. Kyle

    This is a great tutorial as I have been looking for a way to dynamically serve content (page counter mostly) but have not been able to find a good solution. I successfully got it working with a ‘dynamic-cached-content’ tag but because I had to enable ‘late init’, the site was crawling around. After trying your work around it seems to be working great, however, I noticed with IE9 that refreshing or revisiting a cached page, the javascript isn’t ran again. it seems to be caching the page locally, whereas safari (mu usual browser) always pulls a cached file from the server. I know this isn’t a problem with your posted fix, however it still defeats the purpose of dynamic cached content.

  28. Kyle

    Well I think I found a fix for this problem. Looks like IE9 was caching the GET requests so since the same parameters were being sent, it was using the local cache. My solution was to add a third parameter which simply passes a random number:

    Math.floor( Math.random()*99999 )

    This ensures that the local cached copy is not used. So far this seems to be working well on my pages and returns updated values after refreshing or clicking a previously viewed page.

[go to first comment]

Leave a Reply