How does W3 Total Cache CDN URL rewrites work? [closed]

The W3 Total Cache plugin changes the URL of various files in /w3-total-cache/lib/W3/Plugin/Cdn.php in the function *ob_callback*. It uses a series of callbacks to modify an output buffer. The code runs like this:

  • w3_total_cache.php calls $root->run();
  • W3_Root::run calls $plugin->run() for each plugin in $this->_loaded_plugins
  • W3_Plugin_TotalCache::run starts an output buffer which calls W3_Plugin_TotalCache::ob_callback
  • W3_Plugin_TotalCache::ob_callback calls w3tc_do_ob_callbacks() which runs any callback stored in $GLOBALS['_w3tc_ob_callbacks']
  • The CDN adds it’s own callback to that global in W3_Plugin_Cdn::run. That callback is W3_Plugin_Cdn::ob_callback

Which callbacks then run is unfortunately hard coded in this line:
$buffer = w3tc_do_ob_callbacks(array('minify', 'newrelic', 'cdn', 'browsercache', 'pagecache'), $buffer);

Because this is hard coded, if you ever need to modify what is included and what isn’t, you’ll have to change their callback.

Example:

I have a plugin that exports JSON, and the CDN aspect of W3 Total Cache wasn’t changing any URLs for JSON requests. It turns out that my output was failing the w3_is_xml($buffer) test.

I fixed it by turning their single CDN callback into multiples, like this:

// Modify the output buffer callbacks of W3 Total Cache to work with the JSON API
if (!empty($GLOBALS['_w3tc_ob_callbacks']) && isset($GLOBALS['_w3tc_ob_callbacks']['cdn'])) {
    // Back-up the original value of $GLOBALS['_w3tc_ob_callbacks']['cdn']
    // This should be W3_Plugin_Cdn::ob_callback
    $this->cdn_ob_callback = $GLOBALS['_w3tc_ob_callbacks']['cdn'];

    // Replace $GLOBALS['_w3tc_ob_callbacks']['cdn'] with out own method
    // which will call the original callback in between two of our own
    $GLOBALS['_w3tc_ob_callbacks']['cdn'] = array($this, 'do_multiple_cdn_ob_callbacks');
}

Then doing the changes I need, making sure to call their original callback in the middle.

public function do_multiple_cdn_ob_callbacks(&$buffer) {
    // Frist run our own callback to add an XML string to the buffer
    // so that the content passes the w3_is_xml($buffer) test
    $buffer = $this->w3_total_cache_ob_callback_start($buffer);

    // Next run the original callback, which will replace the asset URLs
    $buffer = call_user_func($this->cdn_ob_callback, $buffer);

    // Finally, run another callback of our own to remove the XML string
    $buffer = $this->w3_total_cache_ob_callback_end($buffer);
    return $buffer;
}

Leave a Comment