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:
- 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.
- 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:
- A visitor comes to your site requesting a document.
- .htaccess redirects them to a static HTML file with Javascript code inside of it.
- The visitor downloads the said HTML file
- 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.
- The server receives the special HTTP request, the PHP code executes, and the result is sent back to the client side Javascript
- 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:
- 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.
- We have passed the key “omni_action” along with the value “ts_and_ref” to get omni_request_handler() to intercept this HTTP request.
- 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.
- On successful PHP code execution, we are dynamically writing the server response into the <div id=”omni_div”></div> block using jQuery.
Drawbacks
- It is AJAX so it will not work for people who browse with Javascript disabled.
- 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.
commented on February 24th, 2009 at 7:52 am
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.
commented on February 25th, 2009 at 5:06 am
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.
commented on February 27th, 2009 at 4:54 pm
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? 🙂
commented on February 27th, 2009 at 6:57 pm
@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.
commented on February 27th, 2009 at 9:03 am
I prefer SACK as the implementation or even better a mix of JS+PHP
commented on March 8th, 2009 at 9:17 am
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
commented on March 8th, 2009 at 9:36 am
@BillPatrianakos
Intense Debate is done entirely by Javascript so it definitely works with WP Super Cache out of the box.
commented on March 10th, 2009 at 12:51 am
What is SACK implementation that ajay has mentioned. Do you know Ty?
commented on March 11th, 2009 at 3:09 am
I followed the tutorial in the codex – http://codex.wordpress.org/AJAX_in_Plugins
commented on May 15th, 2009 at 10:20 am
Thanks good article.
commented on June 6th, 2009 at 1:53 am
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!
commented on June 6th, 2009 at 3:30 am
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.
commented on June 29th, 2009 at 8:20 pm
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.
commented on July 31st, 2009 at 1:01 pm
And there's a way to make the plugin Disqus compatible with WP Super Cache!?
commented on July 31st, 2009 at 3:43 pm
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?
commented on August 29th, 2009 at 2:26 pm
Hi Ty, 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?
commented on August 31st, 2009 at 11:10 pm
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.
commented on January 26th, 2010 at 1:48 pm
Does Using Wp minify will do, instead of embedding above codes???
commented on March 5th, 2010 at 3:40 am
Thanks for this post. I have been having a few problems with WP Super and this will help me solve them.
commented on November 25th, 2010 at 6:25 am
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?
commented on November 25th, 2010 at 8:00 am
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.
commented on November 26th, 2010 at 10:13 pm
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.
commented on June 26th, 2011 at 7:28 am
Hi Ty,
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
commented on June 27th, 2011 at 10:12 pm
Not too shabby at all! Great job!
commented on December 18th, 2012 at 10:03 am
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.
commented on February 10th, 2013 at 6:28 am
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?
commented on July 11th, 2013 at 8:06 pm
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.
commented on July 11th, 2013 at 9:44 pm
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.
commented on November 17th, 2014 at 7:32 am
Warning! The superglobals $_GET and $_REQUEST are already decoded. Using urldecode() on an element in $_GET or $_REQUEST could have unexpected and dangerous results.
commented on November 18th, 2014 at 2:55 am
Don’t forget to set the correct Content-Type on your Ajax replies.
header( “Content-Type: application/json” );
Also, beware that (“#omni_div”).html(response); in the example should be $(“#omni_div”).html(response); with the dollar sign so that it is in the scope of jQuery.