Edit: There’s a small bug in WP Super Cache 1.4.2 in the file dynamic-cache-test.php. Grab the development version instead. The documentation in the filehas been updated too. Copy the new file into wp-content/plugins/wp-super-cache/plugins/.
With the next release of WP Super Cache in a day or two the long awaited move away from mfunc, mclude and friends will be complete.
This means that if you have been using mfunc, mclude or dynamic-cached-content the dynamic portions of your sites will go blank if you upgrade WP Super Cache without updating that dynamic code. This may seem complicated but there’s an example script included and detailed explanations below. A lot of effort was made to make this backwards compatible but unfortunately it wasn’t possible.
In their place is a new cacheaction filter called wpsc_cachedata and it’s sidekick wpsc_cachedata_safety. In the future when a site owner using WP Super Cache wants to make part of their website dynamic they will use those filters to modify pre-defined text strings and replace them with the data they want displayed to the end user. There’s an example script ready to be ripped apart to help you figure all this out.
There are two ways of using this:
- The dynamic content is the return value of a simple process, be it
date()
or any of the numerous get_*() functions in WordPress. That data can simply be slotted in place of the pre-defined text mentioned above. - The more difficult bit comes when you need to use an output buffer to collect the data for display. Due to a limitation in PHP it’s impossible to run an output buffer in the callback function of another output buffer, which is when the wpsc_cachedata filter runs. We need to collect that data before the callback function executes.
The first way above is easy. Simply add a text string of random characters to your theme where you want the dynamic content to appear, then hook a function on to the wpsc_cachedata filter to str_replace() it with your dynamic content. These functions in this script do that:
- dynamic_cache_test_filter()
- dynamic_cache_test_template_tag()
- dynamic_cache_test_init()
You’ll have to hook on to the wpsc_cachedata_safety action and return the numeral 1 to actually run the wpsc_cachedata filter. This is a fail safe used by the plugin to make sure things are ok when the filter runs.
Unfortunately if you want to use an output buffer it’s a lot harder. As stated above an output buffer can’t run in the callback function of another output buffer. This means you have to generate your dynamic content earlier in the PHP process.
This could be as easy as calling your dynamic content function (dynamic_output_buffer_test() in the example script) from the wp_footer action, or calling it from any action before shutdown, whichever is appropriate. You’ll also need to add a template tag of your own choosing to your theme.
Your dynamic content function will run just fine for cached pages, so after the new page has run it store the output in a constant or global variable that the same function can look for when the wpsc_cacheaction runs it. If it finds that information, it can do the search and replace of your template tag without running the output buffer again.
Some pages won’t run the dynamic content function however. This includes feeds and sitemaps. Those pages will generate a PHP error because the output buffer will try to run in the callback!
“PHP Fatal error: ob_start(): Cannot use output buffering in output buffering display handlers in…”
To stop that happening you must check that there’s text to shove in the cached page. That’s what happens in the function dynamic_output_buffer_test_safety() in the example script. It fires on the wpsc_cachedata_safety filter and returns the numeral 1 if successful.
Anatomy of an Output Buffer
I’m going to try and explain the output buffer functions in more detail here. They’re only example functions and if you spot a bug or can suggest improvements please do!
define( 'DYNAMIC_OUTPUT_BUFFER_TAG', '' ); // CHANGE THIS!
This is the string you add to your theme where your dynamic content will appear. The plugin will replace this string with the code generated by your dynamic content function.
function dynamic_output_buffer_test( &$cachedata = 0 ) { if ( defined( 'DYNAMIC_OB_TEXT' ) ) return str_replace( DYNAMIC_OUTPUT_BUFFER_TAG, DYNAMIC_OB_TEXT, $cachedata ); ob_start(); // call the sidebar function, do something dynamic echo "<p>This is a test. The current time on the server is: " . date( 'H:i:s' ) . "</p>"; $text = ob_get_contents(); ob_end_clean(); if ( $cachedata === 0 ) // called directly from the theme so store the output define( 'DYNAMIC_OB_TEXT', $text ); else // called via the wpsc_cachedata filter. We only get here in cached pages in wp-cache-phase1.php return str_replace( DYNAMIC_OUTPUT_BUFFER_TAG, $text, $cachedata ); } add_cacheaction( 'wpsc_cachedata', 'dynamic_output_buffer_test' );
- 1. dynamic_output_buffer_test() is the function that will generate the dynamic content inserted into the cached page.
- 2-3. DYNAMIC_OB_TEXT stores the previous output of this function. If it’s set that means we’re in the wpsc_cachedata filter and should insert it into the cached page and return it.
- 4-8. This creates the output buffer, echoes a string with the current time and copies the buffer into a variable called $text before closing the output buffer.
- 10-13. This is how we decide to return data. If $cachedata is 0 that means this function was called from the theme so we should define the constant DYNAMIC_OB_TEXT for later use. Otherwise, we must be dealing with an already cached page so insert the dynamic content into the page and return it.
- 16. Add the dynamic_output_buffer_test() function to the wpsc_cachedata action. “add_cacheaction” is used as it loads before the regular WordPress action code loads.
function dynamic_output_buffer_test_safety( $safety ) { if ( defined( 'DYNAMIC_OB_TEXT' ) ) return 1; // ready to replace tag with dynamic content. else return 0; // tag cannot be replaced. } add_cacheaction( 'wpsc_cachedata_safety', 'dynamic_output_buffer_test_safety' );
- 1-6. dynamic_output_buffer_test_safety() is a function that checks if dynamic content was generated correctly. If that constant is not defined the output buffer will run within the callback function of the main WP Super Cache output buffer and generate a PHP error.
- 7. Add the wpsc_cachedata_safety() function to the dynamic_output_buffer_test_safety action.
sidebar.php
As an example, if I wanted to display my dynamic content in the sidebar of my blog I would load sidebar.php in my theme’s directory and add the following code.
if ( function_exists( 'dynamic_output_buffer_test' ) ) dynamic_output_buffer_test(); ?>WORDPRESS ROCKS THE WORLD<?php
I had previously edited the example script, uncommented it and changed the appropriate tag:
define( 'DYNAMIC_OUTPUT_BUFFER_TAG', 'WORDPRESS ROCKS THE WORLD' ); // CHANGE THIS!
Final Note and Download Link
Please grab the development version of the plugin and try it on a staging server before you put it live. Feedback would be appreciated!
Warning! Keep the tags you use secret. You don’t want someone leaving a comment on your blog with that string! Do not use the same function names or constant names as in this post or example script. They’re in this very public post. Someone is bound to use them and cause you problems when you install their plugin.
Finally, barring any last minute major bugs this version of WP Super Cache will be released on Wednesday. Be careful upgrading. Pass the word around if you know someone is using mfunc as their site will stop working!
Inspiring work, thanks for the detailed breakdown too.
Thanks Donnacha, always nice to get positive feedback!
Hi Donnacha and a very big thank you for WP Super Cache and your continued support and improvements to it.
1. To use these new functions do I need to run PHP mode or can I continue with mod_rewrite?
2. I’m currently working on a site that I have painstakingly been converting all dynamic portions into ajax calls. Would you recommend that I try out these new functions or continue with creating ajax calls?
Many thanks,
wpmnger
wpmnger – you’ve always needed to use PHP mode or legacy mode for the dynamic content functionality, so there’s no change there.
I would say stick with the ajax calls or use a more granular caching system. A full page caching plugin like WP Super Cache or W3 Total Cache cache the whole page. If you use an object cache like memcached or any of the mysql caching plugins on wordpress.org it might be easier to keep parts of the page dynamic.
Having said that, the filter based system here works well and I’m happier about it than the old eval() method which wasn’t that secure.
Thanks Donncha!
For this project I have to use a full page caching system as the server doesn’t support object caching. On other projects I use APC and memcache and am happy with the results.
ajax it is!
Thanks again,
So just for my own clarity. If I use WP Super Cache in a std install, do not modify any of my theme and use the plugins settings the upgrade should be fairly straight forward right?
Yes. If you don’t use the dynamic functionality then it’ll be a straight forward upgrade. It even has a few bug fixes and improvements too!
Superb – upgrade went very smooth. However, when I tried to post to my site using Windows Live Writer I was getting back a 500 error on xmlrpc.php. Since one of my first steps to troubleshooting these types of things is to switch off recently update plugins. So after turning off the caching my post uploaded without issue to the site.
Any idea how the two may be related?
Thanks.
Rich
I added a new feature this evening. If wp-cache-phase1.php hasn’t been loaded when the settings page loads the plugin will recreate advanced-cache.php and fix the WPCACHEHOME constant in wp-config.php.
It does check if caching is enabled as part of that check but if there’s an error in advanced-cache.php it should be caught!
I’m nervous about tomorrow’s release. Getting people to test the plugin is hard and when a release is made suddenly tens of thousands of sites are running brand new code. *gulp*
How can I become a tester and help out?
All you have to do is download the development version I’ve linked to above, but it doesn’t matter now as I released version 1.4 this morning. 🙂
That’s annoying. Have you got PHP error logging enabled? Is there an error in your error_log? Did it take longer than usual to post or did the 500 error show immediately?
If it took longer it could be because of deleting the category and tag archive pages.
Hey Donncha, I was wondering that when I use the “mode_rewrite” cache and some pages don’t have super cache pages created. That’s not a problem for me, but I came up with an idea/question.
Could I somehow say to the plugin that fallback to PHP-cache on this page “X”? Then I would use your new awesome wpsc_cachedata filter.
The plugin should automatically fallback to PHP mode if the mod_rewrite rules have failed for some reason. Maybe use the debug log to figure out why it’s not working right?
Ok, got it. 🙂 The reason is because I’m using data outside wordpress via query parameters. eg. http://www.somedomain.com/vod/video_id=15.
By the way it would very nice have some error filter when caching page, which would stop the process and no cache would be created if a “red” flag was issued through the error filter. Or is this allready implemented and I missed this one?
There is a way. Have your code define the constant DONOTCACHEPAGE and the plugin won’t cache that page.
The GET parameters might screw things up but give it a try anyway.
Oh my! Man I file dump not thinking that much out of a box. Thanks a bunch!
Donncha is the sidebar.php sample code correct, “?>WORDPRESS ROCKS THE WORLD<?php".
Also can you make a tutorial for us users that are not as tech savvy. I am a little lost.
Rafael – yes, but you might have to adjust opening and closing PHP tags according to where you put that snippet.
Currently I don’t have time to do a more detailed tutorial unfortunately.
Hey Donncha,
My site is currently using your WP-Super-Cache plugin and it is affecting Uber menu from working. How would you implement wpsc_cachedata so that the plugin does not interfere with Uber menu? Thanks!
Danny – I’ve never used Uber menu so I can’t help much. Sorry.
Thanks man, I choose to disable Uber menu. Thanks for your response.
look at ubermenu’s documentation. they have a work around for this that bypassed the wordpress menu load and load the ubermenu direct. just search their documentation for ‘cache’
I’ve updated the test plugin in trunk. You can upload that to your WP Super Cache install in the plugins directory to try it.
Comments have been improved and the code rearranged to make it easier to understand.
Where exactly should ‘wpsc_cachedata’ filter be called?
It seems to be called before a shortcode has been parsed.
Possible to call it after that?
We are struggling to use this new dynamic caching method to keep the contents/output of a shortcode dynamic in a post/page and it seems things have been complicated now.
Any suggestions as to how we can go about not caching the contents of a shortcode?
Contrid – it’s called when the cached page is generated, so it should be after the shortcode.
For generating the cached page your shortcode should print the placeholder tag and generate the text that will replace it but it will store that text in a constant or variable. Then when the wpsc_cachedata filter fires replace the placeholder tag with that text.
On already cached pages the function that is called by wpsc_cachedata needs to generate the text.
See my updated the dynamic cache test script in trunk above. It’s better laid out than the version released.
Excellent, thank you for the guidance, I think this will work.
We’ll try it out and post back here.
Still doesn’t seem to work…
We call this in our plugin file:
add_cacheaction( ‘wpsc_cachedata’….);
When this filter is run where the $cachedata is filtered, the global variables or constants we set at the shortcode are not yet available. They are only available after this filter has run.
I assume add_cacheaction( ‘wpsc_cachedata’…) has to be hooked onto some WordPress hook? init or plugins_loaded maybe?
Btw… there is a height and overflow CSS style on your comment box breaking it when a long comment is typed: http://goo.gl/1UWcbe
Contrid – thanks for the tip about the height and overflow. I never noticed that there before but the box seems to adjust as I type text. Did you copy the text of your comment into the comment area?
Anyway, are you talking about a newly generated page or an already cached page? If it’s an already cached page you’ll see from wp-cache-phase1.php that wpsc_cachedata is called early on so the shortcode won’t have run. Your filter function will have to generate the dynamic data.
I’ve implemented this in a similar fashion to the example and found that the homepage cache is still intermittently overwriting the dynamic content. I’m attempting to use it to serve up regional phone numbers that are being selected initially based on a url parameter and then after the first page based on a cookie. I’ve done the same thing on another site using an iframe for the regional phone number instead of modifying the cache and it seems to be bullet-proof. At this point I’m giving up on dynamic caching and opting for the other to avoid the intermittent homepage issue and also so I can go back to using the mod rewrite which is what my cdn recommends to work with their system. While I know enough code to be dangerous I really don’t know the ends and outs of how you make caching work so this may seem like an unrealistic request, but it would be great to be able to have dynamic content capabilities when the mod rewrite is selected.
Hi Donncha,
I’m testing the dynamic cache, using the output buffer. My problem is that it’s working only with Google Chrome, as the string replacement is not being performed in Firefox, nor in Internet Explorer. Unfortunately, I can’t spot any error and the cache logs the same lines after a page request made by Chrome and a page request made by Firefox or IE.
Do you have any idea of why this happens?
Hi Donncha, I’ve got Example 2 from your dynamic-cache-test.php page working successfully. I’m just wondering if it’s possible to modify to have two non-cached elements running at the same time?
For example, could I run both date() and rand() on the same page in different sections and have them both work with dynamic caching?