WordPress filter to modify final html output

Viewed 57800

WordPress has great filter support for getting at all sorts of specific bits of content and modifying it before output. Like the_content filter, which lets you access the markup for a post before it's output to the screen.

I'm trying to find a catch-all filter that gives me one last crack at modifying the final markup in its entirety before output.

I've browsed the list of filters a number of times, but nothing jumps out at me: https://codex.wordpress.org/Plugin_API/Filter_Reference

Anyone know of one?

11 Answers

AFAIK, there is no hook for this, since the themes uses HTML which won't be processed by WordPress.

You could, however, use output buffering to catch the final HTML:

<?php
// example from php.net
function callback($buffer) {
  // replace all the apples with oranges
  return (str_replace("apples", "oranges", $buffer));
}
ob_start("callback");
?>
<html><body>
<p>It's like comparing apples to oranges.</p>
</body></html>
<?php ob_end_flush(); ?>
/* output:
   <html><body>
   <p>It's like comparing oranges to oranges.</p>
   </body></html>
*/

I was using the top solution of this post (by kfriend) for a while. It uses an mu-plugin to buffer the whole output.

But this solution breaks the caching of wp-super-cache and no supercache-files are generated when i upload the mu-plugin.

So: If you are using wp-super-cache, you can use the filter of this plugin like this:

add_filter('wp_cache_ob_callback_filter', function($buffer) {
    $buffer = str_replace('foo', 'bar', $buffer);
    return $buffer;
});

Modified https://stackoverflow.com/users/419673/kfriend answer.

All code will be on functions.php. You can do whatever you want with the html on the "final_output" filter.

On your theme's 'functions.php'

//we use 'init' action to use ob_start()
add_action( 'init', 'process_post' );

function process_post() {
     ob_start();
}


add_action('shutdown', function() {
    $final = '';

    // We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
    // that buffer's output into the final output.
    $levels = ob_get_level();

    for ($i = 0; $i < $levels; $i++) {
        $final .= ob_get_clean();
    }

    // Apply any filters to the final output
    echo apply_filters('final_output', $final);
}, 0);

add_filter('final_output', function($output) {
    //this is where changes should be made
    return str_replace('foo', 'bar', $output); 
});

You might try looking in the wp-includes/formatting.php file. For example, the wpautop function. If you are looking for doing something with the entire page, look at the Super Cache plugin. That writes the final web page to a file for caching. Seeing how that plug-in works may give you some ideas.

Indeed there was a discusussion recently on the WP-Hackers mailing list about the topic of full page modification and it seems the consensus was that output buffering with ob_start() etc was the only real solution. There was also some discussion about the upsides and downsides of it: http://groups.google.com/group/wp-hackers/browse_thread/thread/e1a6f4b29169209a#

To summarize: It works and is the best solution when necessary (like in the WP-Supercache plugin) but slows down overall speeds because your content isn't allowed to be sent to the browser as its ready, but instead has to wait for the full document to be rendered (for ob_end() ) before it can be processed by you and sent to the browser.

To simplify previous answers, just use this in functions.php:

ob_start();
add_action('shutdown', function () {
    $html = ob_get_clean();
    // ... modify $html here
    echo $html;
}, 0);

I've been testing the answers here now for a while, and since the cache breaking thing is still an issue, I came up with a slightly different solution. In my tests no page cache broke. This solution has been implemented into my WordPress plugin OMGF (which has 50k+ users right now) and no issues with page cache breaking has been reported.

First, we start an output buffer on template redirect:

add_action('template_redirect', 'maybe_buffer_output', 3);
function maybe_buffer_output()
{
    /**
     * You can run all sorts of checks here, (e.g. if (is_admin()) if you don't want the buffer to start in certain situations.
     */

    ob_start('return_buffer');
}

Then, we apply our own filter to the HTML.

function return_buffer($html)
{
    if (!$html) {
        return $html;
    }

    return apply_filters('buffer_output', $html);
}

And then we can hook into the output by adding a filter:

add_filter('buffer_output', 'parse_output');
function parse_output($html)
{
    // Do what you want. Just don't forget to return the $html.

    return $html;
}

Hope it helps anyone.

I have run into problems with this code, as I end up with what seems to be the original source for the page so that some plugins has no effect on the page. I am trying to solve this now - I haven't found much info regarding best practises for collecting the output from WordPress.

Update and solution:

The code from KFRIEND didnt work for me as this captures unprocessed source from WordPress, not the same output that ends up in the browser in fact. My solution is probably not elegant using a globals variable to buffer up the contents - but at least I know get the same collected HTML as is delivered to the browser. Could be that different setups of plugins creates problems but thanks to code example by Jacer Omri above I ended up with this.

This code is in my case located typically in functions.php in theme folder.

$GLOBALS['oldschool_buffer_variable'] = '';
function sc_callback($data){
    $GLOBALS['final_html'] .= $data;
    return $data;
}
function sc_buffer_start(){
    ob_start('sc_callback');
}
function sc_buffer_end(){
    // Nothing makes a difference in my setup here, ob_get_flush() ob_end_clean() or whatever
    // function I try - nothing happens they all result in empty string. Strange since the
    // different functions supposedly have very different behaviours. Im guessing there are 
    // buffering all over the place from different plugins and such - which makes it so 
    // unpredictable. But that's why we can do it old school :D
    ob_end_flush();

    // Your final HTML is here, Yeeha!
    $output = $GLOBALS['oldschool_buffer_variable'];
}
add_action('wp_loaded', 'sc_buffer_start');
add_action('shutdown', 'sc_buffer_end');
Related