Make WordPress Core

#58285 closed feature request (duplicate)

New filter hook for filtering whole HTML before final output

Reported by: kubiq's profile kubiq Owned by:
Milestone: Priority: normal
Severity: normal Version: 6.2
Component: General Keywords:
Focuses: performance Cc:

Description

Let's say I have 1 cache plugin that is great in cache but has no or bad CSS and JS combining and minification options, so I have another plugin to do that... than I have another plugin that is removing some unnecessary styles and scripts from HTML on some pages and then I have another one that fixes some width, height, loading, alt attributes...

These plugins use their own ob_starts again and again, flushing, etc... some of them use some preg_match and replaces, some use domdocument parser and then the website is very slow for administrator or on first load before cache are created.

Currently, most cache, optimizing, assets plugins do something like this

<?php
add_action( 'init', function(){
        ob_start( 'my_magic_function' );
}, -1 );

function my_magic_function( $html ){
        // preg_replaces
        // maybe domdocument parsing
        return $html;
}

and this is done again and again by every such-a-plugin

That's why I think it should be part of the core and I imagine something like this:

<?php
add_action( 'init', function(){
        if( apply_filters( 'wp_enable_final_html_filtering', false ) ){
                ob_start( 'default_wp_ob_handler' );
        }
}, -1 );

function default_wp_ob_handler( $html ){
        $dom = false;
        if( apply_filters( 'wp_enable_final_html_dom', false ) ){
                $dom = new DOMDocument();
                $dom->loadHTML( $html );
                $dom = apply_filters( 'wp_filter_final_html_dom', $dom );
                $html = $dom->saveHTML();
        }
        return apply_filters( 'wp_filter_final_html', $html );
}

so then plugin developers can easily access html or DOM and change it without the need to use ob_starts and flushes and parsers again and again, eg.:

<?php
add_filter( 'wp_enable_final_html_filtering', '__return_true' );
add_filter( 'wp_filter_final_html', function( $html ){
        // preg_mathces and replacements
        return $html;
});

// or if you need work with DOM structure

add_filter( 'wp_enable_final_html_filtering', '__return_true' );
add_filter( 'wp_filter_final_html_dom', function( $dom ){
        // xpath searches and replacements
        return $dom;
}, 10, 2 );

This can improve loading performance for admin users, or on the first load of page before cache is created, it will be more consistent and all cache and optimizing plugins can use these hooks easily then.

Change History (5)

#1 @westonruter
19 months ago

Note that the topic of output buffering the page output has come up on the May 2nd Performance Chat. See also Slack thread, as well as a thread in core chat. I have on my todo list to research usage of output buffering in plugins, namely premature flushing. But I see there is another issue that @aristath raised regarding error handling and progressive rendering.

This ticket was mentioned in Slack in #core by westonruter. View the logs.


19 months ago

#3 in reply to: ↑ description ; follow-up: @SergeyBiryukov
19 months ago

Hi there, welcome back to WordPress Trac! Thanks for the ticket.

Just noting that this seems related to #43258.

Replying to kubiq:

<?php
add_action( 'init', function(){
        if( apply_filters( 'wp_enable_final_html_filtering', false ) ){
                ob_start( 'default_wp_ob_handler' );
        }
}, -1 );

function default_wp_ob_handler( $html ){
        $dom = false;
        if( apply_filters( 'wp_enable_final_html_dom', false ) ){
                $dom = new DOMDocument();
                $dom->loadHTML( $html );
                $dom = apply_filters( 'wp_filter_final_html_dom', $dom );
                $html = $dom->saveHTML();
        }
        return apply_filters( 'wp_filter_final_html', $html );
}

In this example, the wp_enable_* filters appear to be redundant, I think we could use has_filter() instead:

<?php
add_action( 'init', function(){
        if( has_filter( 'wp_filter_final_html' ) || has_filter( 'wp_filter_final_html_dom' ) ){
                ob_start( 'default_wp_ob_handler' );
        }
}, -1 );

function default_wp_ob_handler( $html ){
        $dom = false;
        if( has_filter( 'wp_filter_final_html_dom' ) ){
                $dom = new DOMDocument();
                $dom->loadHTML( $html );
                $dom = apply_filters( 'wp_filter_final_html_dom', $dom );
                $html = $dom->saveHTML();
        }
        return apply_filters( 'wp_filter_final_html', $html );
}

That way only one hook would be needed to enable the filtering, instead of two.

#4 in reply to: ↑ 3 @kubiq
19 months ago

Thank you SergeyBiryukov, I wasn't able to find anything similar, but you're right, that's the same problem and similar proposed solution.

And you're also right about that has_filter :)

Last edited 19 months ago by kubiq (previous) (diff)

#5 @westonruter
15 months ago

  • Milestone Awaiting Review deleted
  • Resolution set to duplicate
  • Status changed from new to closed

Let's consolidate on #43258 for moving this forward.

Note: See TracTickets for help on using tickets.