Commit 074794e960 for wordpress.org
commit 074794e960d210d8f6a7ef46e103303709d2fe54
Author: Weston Ruter <weston@xwp.co>
Date: Tue Nov 4 05:28:39 2025 +0000
General: Ensure errors can be displayed when triggered during finalization of the template enhancement output buffer.
When `display_errors` (`WP_DEBUG_DISPLAY`) is enabled, errors (including notices, warnings, and deprecations) that are triggered during the `wp_template_enhancement_output_buffer` filter or the `wp_finalized_template_enhancement_output_buffer` action have not been displayed on the frontend since they are emitted in an output buffer callback. Furthermore, as of PHP 8.5 attempting to print anything in an output buffer callback causes a deprecation notice. This introduces an error handler and try/catch block to capture any errors and exceptions that occur during these hooks. If `display_errors` is enabled, these captured errors are then appended to the output buffer so they are visible on the frontend, using the same internal format PHP uses for printing errors. Any exceptions or user errors are converted to warnings so that the template enhancement buffer is not prevented from being flushed.
Developed in https://github.com/WordPress/wordpress-develop/pull/10310
Follow-up to [61111], [61088], [60936].
Props westonruter, dmsnell.
See #43258, #64126.
Fixes #64108.
Built from https://develop.svn.wordpress.org/trunk@61120
git-svn-id: http://core.svn.wordpress.org/trunk@60456 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/template.php b/wp-includes/template.php
index a0d9fd9298..7036bb0047 100644
--- a/wp-includes/template.php
+++ b/wp-includes/template.php
@@ -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 );
+ }
- /**
- * 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;
}
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 2920f66519..1e8e7f3327 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '6.9-beta2-61119';
+$wp_version = '6.9-beta2-61120';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.