How does W3 Total Cache CDN URL rewrites work?

I just setup W3 Total Cache S3 CDN (origin push) and most of the URLs are being rewritten correctly but we have some custom plugins where the URLs are not being rewritten. How do I grab the rewritten URL of W3TC from another plugin? I’m currently using the following code to grab the image:

wp_get_attachment_image_src( get_post_thumbnail_id(), 'car-thumb');

Read More

I’m assuming there is a filter of some sort that just replaces the original “origin” url with the asset URL on the S3 bucket but I’ve been unable to find where in the code.

Related posts

Leave a Reply

3 comments

  1. 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;
    }
    
  2. I had a similar issue when rendering a template part.

    I had to invoke the W3_Plugin_Cdn plugin programmatically.

    As correctly stated by Tyler V., the w3_is_xml($buffer) will fail if $buffer doesn’t look like HTML or XML, that’s why I’m putting <html> at the beginning of the template part.

    Here’s an example on how I did.

    if (class_exists('W3_Plugin_Cdn')) {
        $cdn_handler = new W3_Plugin_Cdn();
        ob_start();
        echo '<html>';
        get_template_part($templatepart, $templatepartdetail);
        $buffer = ob_get_contents();
        $cdn_handler->ob_callback($buffer);
        $buffer = substr($buffer, strlen('<html>'));
        ob_end_clean();
        echo $buffer;
    }
    
  3. I had the same issues and couldn’t get an answer either. I did however find where in the database WP is storing the entries for the attached images and used that to create my own rewritten URLs.

    If you look in the _postmeta table there is a meta_key “_wp_attached_file” that stores the folder path to the attached images. With this information I only needed to add the URL structure for where our S3 bucket is located.

    global $postid;
    $bucketName = "your_bucket_name_goes_here";
    $postThumbNailID = get_post_meta($post->ID, '_thumbnail_id');
    $postThumbNailUrl = get_post_meta($postThumbNailID[0], '_wp_attached_file');
    $s3BucketURL = "http://".$bucketName.".s3.amazonaws.com/files/".$postThumbNailUrl[0];