Skip to content

Commit aefa8b7

Browse files
committed
General: Introduce output buffering for template enhancements.
This introduces an output buffer for the entire template rendering process. This allows for post-processing of the complete HTML output via filtering before it is sent to the browser. This is primarily intended for performance optimizations and other progressive enhancements. Extenders must not rely on output buffer processing for critical content and functionality since a site may opt out of output buffering for the sake of streaming. Extenders are heavily encouraged to use the HTML API as opposed to using regular expressions in output buffer filters. * A new `wp_before_include_template` action is introduced, which fires immediately before the template file is included. This is useful on its own, as it avoids the need to misuse `template_include` filter to run logic right before the template is loaded (e.g. sending a `Server-Timing` header). * The `wp_start_template_enhancement_output_buffer()` function is hooked to this new action. It starts an output buffer, but only if there are `wp_template_enhancement_output_buffer` filters present, or else if there is an explicit opt-in via the `wp_should_output_buffer_template_for_enhancement` filter. * The `wp_finalize_template_enhancement_output_buffer()` function serves as the output buffer callback. It applies `wp_template_enhancement_output_buffer` filters to the buffered content if the response is identified as HTML. * The output buffer callback passes through (without filtering) any content for non-HTML responses, identified by the `Content-Type` response header. * This provides a standardized way for plugins (and core) to perform optimizations, such as removing unused CSS, without each opening their own ad hoc output buffer. Developed in WordPress/wordpress-develop#8412. Props westonruter, nextendweb, dmsnell, flixos90, jorbin, peterwilsoncc, swissspidy, DrewAPicture, DaanvandenBergh, OptimizingMatters, tabrisrp, jonoaldersonwp, SergeyBiryukov. Fixes #43258. Built from https://develop.svn.wordpress.org/trunk@60936 git-svn-id: https://core.svn.wordpress.org/trunk@60272 1a063a9b-81f0-0310-95a4-ce76da25c4cd
1 parent 8fbfeb5 commit aefa8b7

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed

wp-includes/default-filters.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@
422422
add_action( 'do_all_pings', 'generic_ping', 10, 0 );
423423
add_action( 'do_robots', 'do_robots' );
424424
add_action( 'do_favicon', 'do_favicon' );
425+
add_action( 'wp_before_include_template', 'wp_start_template_enhancement_output_buffer' );
425426
add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 );
426427
add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' );
427428
add_action( 'init', 'smilies_init', 5 );

wp-includes/template-loader.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@
113113
*/
114114
$template = apply_filters( 'template_include', $template );
115115
if ( $template ) {
116+
/**
117+
* Fires immediately before including the template.
118+
*
119+
* @since 6.9.0
120+
*
121+
* @param string $template The path of the template about to be included.
122+
*/
123+
do_action( 'wp_before_include_template', $template );
124+
116125
include $template;
117126
} elseif ( current_user_can( 'switch_themes' ) ) {
118127
$theme = wp_get_theme();

wp-includes/template.php

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,3 +823,144 @@ function load_template( $_template_file, $load_once = true, $args = array() ) {
823823
*/
824824
do_action( 'wp_after_load_template', $_template_file, $load_once, $args );
825825
}
826+
827+
/**
828+
* Checks whether the template should be output buffered for enhancement.
829+
*
830+
* By default, an output buffer is only started if a {@see 'wp_template_enhancement_output_buffer'} filter has been
831+
* added be the time a template is included at the {@see 'wp_before_include_template'} action. This allows template
832+
* responses to be streamed as much as possible when no template enhancements are registered to apply.
833+
*
834+
* @since 6.9.0
835+
*
836+
* @return bool Whether the template should be output-buffered for enhancement.
837+
*/
838+
function wp_should_output_buffer_template_for_enhancement(): bool {
839+
/**
840+
* Filters whether the template should be output-buffered for enhancement.
841+
*
842+
* By default, an output buffer is only started if a {@see 'wp_template_enhancement_output_buffer'} filter has been
843+
* added. For this default to apply, a filter must be added by the time the template is included at the
844+
* {@see 'wp_before_include_template'} action. This allows template responses to be streamed as much as possible
845+
* when no template enhancements are registered to apply. This filter allows a site to opt in to adding such
846+
* template enhancement filters during the rendering of the template.
847+
*
848+
* @since 6.9.0
849+
*
850+
* @param bool $use_output_buffer Whether an output buffer is started.
851+
*/
852+
return (bool) apply_filters( 'wp_should_output_buffer_template_for_enhancement', has_filter( 'wp_template_enhancement_output_buffer' ) );
853+
}
854+
855+
/**
856+
* Starts the template enhancement output buffer.
857+
*
858+
* This function is called immediately before the template is included.
859+
*
860+
* @since 6.9.0
861+
*
862+
* @return bool Whether the output buffer successfully started.
863+
*/
864+
function wp_start_template_enhancement_output_buffer(): bool {
865+
if ( ! wp_should_output_buffer_template_for_enhancement() ) {
866+
return false;
867+
}
868+
869+
$started = ob_start(
870+
'wp_finalize_template_enhancement_output_buffer',
871+
0, // Unlimited buffer size so that entire output is passed to the filter.
872+
/*
873+
* Instead of the default PHP_OUTPUT_HANDLER_STDFLAGS (cleanable, flushable, and removable) being used for
874+
* flags, the PHP_OUTPUT_HANDLER_FLUSHABLE flag must be omitted. If the buffer were flushable, then each time
875+
* that ob_flush() is called, a fragment of the output would be sent into the output buffer callback. This
876+
* output buffer is intended to capture the entire response for processing, as indicated by the chunk size of 0.
877+
* So the buffer does not allow flushing to ensure the entire buffer can be processed, such as for optimizing an
878+
* entire HTML document, where markup in the HEAD may need to be adjusted based on markup that appears late in
879+
* the BODY.
880+
*
881+
* If this ends up being problematic, then PHP_OUTPUT_HANDLER_FLUSHABLE could be added to the $flags and the
882+
* output buffer callback could check if the phase is PHP_OUTPUT_HANDLER_FLUSH and abort any subsequent
883+
* processing while also emitting a _doing_it_wrong().
884+
*
885+
* The output buffer needs to be removable because WordPress calls wp_ob_end_flush_all() and then calls
886+
* wp_cache_close(). If the buffers are not all flushed before wp_cache_close() is closed, then some output buffer
887+
* handlers (e.g. for caching plugins) may fail to be able to store the page output in the object cache.
888+
* See <https://github.com/WordPress/performance/pull/1317#issuecomment-2271955356>.
889+
*/
890+
PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE
891+
);
892+
893+
if ( $started ) {
894+
/**
895+
* Fires when the template enhancement output buffer has started.
896+
*
897+
* @since 6.9.0
898+
*/
899+
do_action( 'wp_template_enhancement_output_buffer_started' );
900+
}
901+
902+
return $started;
903+
}
904+
905+
/**
906+
* Finalizes the template enhancement output buffer.
907+
*
908+
* Checks to see if the output buffer is complete and contains HTML. If so, runs the content through
909+
* the `wp_template_enhancement_output_buffer` filter. If not, the original content is returned.
910+
*
911+
* @since 6.9.0
912+
*
913+
* @see wp_start_template_enhancement_output_buffer()
914+
*
915+
* @param string $output Output buffer.
916+
* @param int $phase Phase.
917+
* @return string Finalized output buffer.
918+
*/
919+
function wp_finalize_template_enhancement_output_buffer( string $output, int $phase ): string {
920+
// When the output is being cleaned (e.g. pending template is replaced with error page), do not send it through the filter.
921+
if ( ( $phase & PHP_OUTPUT_HANDLER_CLEAN ) !== 0 ) {
922+
return $output;
923+
}
924+
925+
// Detect if the response is an HTML content type.
926+
$is_html_content_type = null;
927+
$html_content_types = array( 'text/html', 'application/xhtml+xml' );
928+
foreach ( headers_list() as $header ) {
929+
$header_parts = preg_split( '/\s*[:;]\s*/', strtolower( $header ) );
930+
if (
931+
is_array( $header_parts ) &&
932+
count( $header_parts ) >= 2 &&
933+
'content-type' === $header_parts[0]
934+
) {
935+
$is_html_content_type = in_array( $header_parts[1], $html_content_types, true );
936+
break; // PHP only sends the first Content-Type header in the list.
937+
}
938+
}
939+
if ( null === $is_html_content_type ) {
940+
$is_html_content_type = in_array( ini_get( 'default_mimetype' ), $html_content_types, true );
941+
}
942+
943+
// If the content type is not HTML, short-circuit since it is not relevant for enhancement.
944+
if ( ! $is_html_content_type ) {
945+
return $output;
946+
}
947+
948+
$filtered_output = $output;
949+
950+
/**
951+
* Filters the template enhancement output buffer prior to sending to the client.
952+
*
953+
* This filter only applies the HTML output of an included template. This filter is a progressive enhancement
954+
* intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
955+
* depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter are
956+
* highly discouraged from using regular expressions to do any kind of replacement on the output. Use the HTML API
957+
* (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of PHP 8.4 which
958+
* fully supports HTML5.
959+
*
960+
* @since 6.9.0
961+
*
962+
* @param string $filtered_output HTML template enhancement output buffer.
963+
* @param string $output Original HTML template output buffer.
964+
*/
965+
return (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
966+
}

wp-includes/version.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
* @global string $wp_version
1818
*/
19-
$wp_version = '6.9-alpha-60935';
19+
$wp_version = '6.9-alpha-60936';
2020

2121
/**
2222
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.

0 commit comments

Comments
 (0)