Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fcc23c8
Capture errors emitted while applying wp_template_enhancement_output_…
westonruter Oct 16, 2025
623994b
Merge branch 'trunk' into trac-64108-errors-emitted-during-output-buf…
westonruter Oct 27, 2025
f6034e1
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Oct 30, 2025
716a9be
Remove redundant E_ALL
westonruter Oct 30, 2025
d9c167a
Restore error handler only after the wp_send_late_headers action has …
westonruter Oct 30, 2025
27d9e83
Catch exceptions when firing hooks in output buffer callback
westonruter Oct 30, 2025
991e191
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Nov 1, 2025
39c6d92
Improve error handling and add tests
westonruter Nov 1, 2025
ef1b4b4
Display exceptions and user errors as errors not warnings
westonruter Nov 1, 2025
4d7a459
Suppres false positive for phpstan
westonruter Nov 1, 2025
e6029bb
Update action name
westonruter Nov 1, 2025
24b3171
Improve variable assignment location
westonruter Nov 1, 2025
231db4b
Add test coverage for errors in action
westonruter Nov 1, 2025
5607d30
Harmonize catch blocks
westonruter Nov 1, 2025
bb7aeaf
Improve docs about not printing during callbacks
westonruter Nov 1, 2025
e39794e
Remove TODO
westonruter Nov 1, 2025
2a359df
Remove todo
westonruter Nov 1, 2025
dd9bbdf
Opt to display errors which occur during finalize action
westonruter Nov 1, 2025
2fe5542
Improve handling of html_errors and add support for error_prepend_str…
westonruter Nov 2, 2025
cc31d80
Fix phpcs
westonruter Nov 2, 2025
5b13a71
Account for display_errors being stderr
westonruter Nov 2, 2025
2ef5af1
Add test case for display_errors=stderr
westonruter Nov 2, 2025
2d9bdd4
Use Throwable instead of Exception when catching
westonruter Nov 2, 2025
da8e3f6
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Nov 3, 2025
0d580b8
Break up sprintf() into multiple lines
westonruter Nov 3, 2025
a8e7029
Move list of config options to constant
westonruter Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 146 additions & 43 deletions src/wp-includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -965,50 +965,153 @@ function wp_finalize_template_enhancement_output_buffer( string $output, int $ph

$filtered_output = $output;

/**
* Filters the template enhancement output buffer prior to sending to the client.
*
* This filter only applies the HTML output of an included template. This filter is a progressive enhancement
* intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
* depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter are
* highly discouraged from using regular expressions to do any kind of replacement on the output. Use the HTML API
* (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of PHP 8.4 which
* fully supports HTML5.
*
* Important: Because this filter is applied inside an output buffer callback (i.e. display handler), any callbacks
* added to the filter must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal error:
* "Cannot use output buffering in output buffering display handlers."
*
* @since 6.9.0
*
* @param string $filtered_output HTML template enhancement output buffer.
* @param string $output Original HTML template output buffer.
*/
$filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
$did_just_catch = false;

$error_log = array();
set_error_handler(
static function ( int $level, string $message, ?string $file = null, ?int $line = null ) use ( &$error_log, &$did_just_catch ) {
// Switch a user error to an exception so that it can be caught and the buffer can be returned.
if ( E_USER_ERROR === $level ) {
throw new Exception( __( 'User error triggered:' ) . ' ' . $message );
}

// Display a caught exception as an error since it prevents any of the output buffer filters from applying.
if ( $did_just_catch ) { // @phpstan-ignore if.alwaysFalse (The variable is set in the catch block below.)
$level = E_USER_ERROR;
}

// Capture a reported error to be displayed by appending to the processed output buffer if display_errors is enabled.
if ( error_reporting() & $level ) {
$error_log[] = compact( 'level', 'message', 'file', 'line' );
}
return false;
}
);
$original_display_errors = ini_get( 'display_errors' );
if ( $original_display_errors ) {
ini_set( 'display_errors', 0 );
Copy link

@justlevine justlevine Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this nit matters, so asking here before creating a new ticket +PR, but officially for PHP <8.1, the second param should be a string: https://www.php.net/manual/en/function.ini-set.php

image

https://phpstan.org/r/371b5bf7-6e70-45db-9d94-919d1ae49a5e

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks. Yeah, good point. Go ahead and open a PR.

}

/**
* Fires after the template enhancement output buffer has been finalized.
*
* This happens immediately before the template enhancement output buffer is flushed. No output may be printed at
* this action. However, HTTP headers may be sent, which makes this action complimentary to the
* {@see 'send_headers'} action, in which headers may be sent before the template has started rendering. In
* contrast, this `wp_finalized_template_enhancement_output_buffer` action is the possible point at which HTTP
* headers can be sent. This action does not fire if the "template enhancement output buffer" was not started. This
* output buffer is automatically started if this action is added before
* {@see wp_start_template_enhancement_output_buffer()} runs at the {@see 'wp_before_include_template'} action with
* priority 1000. Before this point, the output buffer will also be started automatically if there was a
* {@see 'wp_template_enhancement_output_buffer'} filter added, or if the
* {@see 'wp_should_output_buffer_template_for_enhancement'} filter is made to return `true`.
*
* Important: Because this action fires inside an output buffer callback (i.e. display handler), any callbacks added
* to the action must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal error:
* "Cannot use output buffering in output buffering display handlers."
*
* @since 6.9.0
*
* @param string $output Finalized output buffer.
*/
do_action( 'wp_finalized_template_enhancement_output_buffer', $filtered_output );
try {
/**
* Filters the template enhancement output buffer prior to sending to the client.
*
* This filter only applies the HTML output of an included template. This filter is a progressive enhancement
* intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
* depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter
* are highly discouraged from using regular expressions to do any kind of replacement on the output. Use the
* HTML API (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of
* PHP 8.4 which fully supports HTML5.
*
* Do not print any output during this filter. While filters normally don't print anything, this is especially
* important since this applies during an output buffer callback. Prior to PHP 8.5, the output will be silently
* omitted, whereas afterward a deprecation notice will be emitted.
*
* Important: Because this filter is applied inside an output buffer callback (i.e. display handler), any
* callbacks added to the filter must not attempt to start their own output buffers. Otherwise, PHP will raise a
* fatal error: "Cannot use output buffering in output buffering display handlers."
*
* @since 6.9.0
*
* @param string $filtered_output HTML template enhancement output buffer.
* @param string $output Original HTML template output buffer.
*/
$filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
} catch ( Throwable $throwable ) {
// Emit to the error log as a warning not as an error to prevent halting execution.
$did_just_catch = true;
trigger_error(
sprintf(
/* translators: %s is the throwable class name */
__( 'Uncaught "%s" thrown:' ),
get_class( $throwable )
) . ' ' . $throwable->getMessage(),
E_USER_WARNING
);
$did_just_catch = false;
}

try {
/**
* Fires after the template enhancement output buffer has been finalized.
*
* This happens immediately before the template enhancement output buffer is flushed. No output may be printed
* at this action; prior to PHP 8.5, the output will be silently omitted, whereas afterward a deprecation notice
* will be emitted. Nevertheless, HTTP headers may be sent, which makes this action complimentary to the
* {@see 'send_headers'} action, in which headers may be sent before the template has started rendering. In
* contrast, this `wp_finalized_template_enhancement_output_buffer` action is the possible point at which HTTP
* headers can be sent. This action does not fire if the "template enhancement output buffer" was not started.
* This output buffer is automatically started if this action is added before
* {@see wp_start_template_enhancement_output_buffer()} runs at the {@see 'wp_before_include_template'} action
* with priority 1000. Before this point, the output buffer will also be started automatically if there was a
* {@see 'wp_template_enhancement_output_buffer'} filter added, or if the
* {@see 'wp_should_output_buffer_template_for_enhancement'} filter is made to return `true`.
*
* Important: Because this action fires inside an output buffer callback (i.e. display handler), any callbacks
* added to the action must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal
* error: "Cannot use output buffering in output buffering display handlers."
*
* @since 6.9.0
*
* @param string $output Finalized output buffer.
*/
do_action( 'wp_finalized_template_enhancement_output_buffer', $filtered_output );
} catch ( Throwable $throwable ) {
// Emit to the error log as a warning not as an error to prevent halting execution.
$did_just_catch = true;
trigger_error(
sprintf(
/* translators: %s is the class name */
__( 'Uncaught "%s" thrown:' ),
get_class( $throwable )
) . ' ' . $throwable->getMessage(),
E_USER_WARNING
);
$did_just_catch = false;
}

// Append any errors to be displayed before returning flushing the buffer.
if ( $original_display_errors && 'stderr' !== $original_display_errors ) {
foreach ( $error_log as $error ) {
switch ( $error['level'] ) {
case E_USER_NOTICE:
$type = 'Notice';
break;
case E_USER_DEPRECATED:
$type = 'Deprecated';
break;
case E_USER_WARNING:
$type = 'Warning';
break;
default:
$type = 'Error';
}

if ( ini_get( 'html_errors' ) ) {
/*
* Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1478>.
* The self-closing tags are a vestige of the XHTML past!
*/
$format = "%s<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%s</b><br />\n%s";
} else {
// Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1492>.
$format = "%s\n%s: %s in %s on line %s\n%s";
}
$filtered_output .= sprintf(
$format,
ini_get( 'error_prepend_string' ),
$type,
$error['message'],
$error['file'],
$error['line'],
ini_get( 'error_append_string' )
);
}

ini_set( 'display_errors', $original_display_errors );
}

restore_error_handler();

return $filtered_output;
}
Loading
Loading