Skip to content

Commit 5b03f1f

Browse files
committed
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 #10310 Follow-up to [61111], [61088], [60936]. Props westonruter, dmsnell. See #43258, #64126. Fixes #64108. git-svn-id: https://develop.svn.wordpress.org/trunk@61120 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 972d21c commit 5b03f1f

File tree

2 files changed

+546
-50
lines changed

2 files changed

+546
-50
lines changed

src/wp-includes/template.php

Lines changed: 146 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -965,50 +965,153 @@ function wp_finalize_template_enhancement_output_buffer( string $output, int $ph
965965

966966
$filtered_output = $output;
967967

968-
/**
969-
* Filters the template enhancement output buffer prior to sending to the client.
970-
*
971-
* This filter only applies the HTML output of an included template. This filter is a progressive enhancement
972-
* intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
973-
* depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter are
974-
* highly discouraged from using regular expressions to do any kind of replacement on the output. Use the HTML API
975-
* (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of PHP 8.4 which
976-
* fully supports HTML5.
977-
*
978-
* Important: Because this filter is applied inside an output buffer callback (i.e. display handler), any callbacks
979-
* added to the filter must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal error:
980-
* "Cannot use output buffering in output buffering display handlers."
981-
*
982-
* @since 6.9.0
983-
*
984-
* @param string $filtered_output HTML template enhancement output buffer.
985-
* @param string $output Original HTML template output buffer.
986-
*/
987-
$filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
968+
$did_just_catch = false;
969+
970+
$error_log = array();
971+
set_error_handler(
972+
static function ( int $level, string $message, ?string $file = null, ?int $line = null ) use ( &$error_log, &$did_just_catch ) {
973+
// Switch a user error to an exception so that it can be caught and the buffer can be returned.
974+
if ( E_USER_ERROR === $level ) {
975+
throw new Exception( __( 'User error triggered:' ) . ' ' . $message );
976+
}
977+
978+
// Display a caught exception as an error since it prevents any of the output buffer filters from applying.
979+
if ( $did_just_catch ) { // @phpstan-ignore if.alwaysFalse (The variable is set in the catch block below.)
980+
$level = E_USER_ERROR;
981+
}
982+
983+
// Capture a reported error to be displayed by appending to the processed output buffer if display_errors is enabled.
984+
if ( error_reporting() & $level ) {
985+
$error_log[] = compact( 'level', 'message', 'file', 'line' );
986+
}
987+
return false;
988+
}
989+
);
990+
$original_display_errors = ini_get( 'display_errors' );
991+
if ( $original_display_errors ) {
992+
ini_set( 'display_errors', 0 );
993+
}
988994

989-
/**
990-
* Fires after the template enhancement output buffer has been finalized.
991-
*
992-
* This happens immediately before the template enhancement output buffer is flushed. No output may be printed at
993-
* this action. However, HTTP headers may be sent, which makes this action complimentary to the
994-
* {@see 'send_headers'} action, in which headers may be sent before the template has started rendering. In
995-
* contrast, this `wp_finalized_template_enhancement_output_buffer` action is the possible point at which HTTP
996-
* headers can be sent. This action does not fire if the "template enhancement output buffer" was not started. This
997-
* output buffer is automatically started if this action is added before
998-
* {@see wp_start_template_enhancement_output_buffer()} runs at the {@see 'wp_before_include_template'} action with
999-
* priority 1000. Before this point, the output buffer will also be started automatically if there was a
1000-
* {@see 'wp_template_enhancement_output_buffer'} filter added, or if the
1001-
* {@see 'wp_should_output_buffer_template_for_enhancement'} filter is made to return `true`.
1002-
*
1003-
* Important: Because this action fires inside an output buffer callback (i.e. display handler), any callbacks added
1004-
* to the action must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal error:
1005-
* "Cannot use output buffering in output buffering display handlers."
1006-
*
1007-
* @since 6.9.0
1008-
*
1009-
* @param string $output Finalized output buffer.
1010-
*/
1011-
do_action( 'wp_finalized_template_enhancement_output_buffer', $filtered_output );
995+
try {
996+
/**
997+
* Filters the template enhancement output buffer prior to sending to the client.
998+
*
999+
* This filter only applies the HTML output of an included template. This filter is a progressive enhancement
1000+
* intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
1001+
* depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter
1002+
* are highly discouraged from using regular expressions to do any kind of replacement on the output. Use the
1003+
* HTML API (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of
1004+
* PHP 8.4 which fully supports HTML5.
1005+
*
1006+
* Do not print any output during this filter. While filters normally don't print anything, this is especially
1007+
* important since this applies during an output buffer callback. Prior to PHP 8.5, the output will be silently
1008+
* omitted, whereas afterward a deprecation notice will be emitted.
1009+
*
1010+
* Important: Because this filter is applied inside an output buffer callback (i.e. display handler), any
1011+
* callbacks added to the filter must not attempt to start their own output buffers. Otherwise, PHP will raise a
1012+
* fatal error: "Cannot use output buffering in output buffering display handlers."
1013+
*
1014+
* @since 6.9.0
1015+
*
1016+
* @param string $filtered_output HTML template enhancement output buffer.
1017+
* @param string $output Original HTML template output buffer.
1018+
*/
1019+
$filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
1020+
} catch ( Throwable $throwable ) {
1021+
// Emit to the error log as a warning not as an error to prevent halting execution.
1022+
$did_just_catch = true;
1023+
trigger_error(
1024+
sprintf(
1025+
/* translators: %s is the throwable class name */
1026+
__( 'Uncaught "%s" thrown:' ),
1027+
get_class( $throwable )
1028+
) . ' ' . $throwable->getMessage(),
1029+
E_USER_WARNING
1030+
);
1031+
$did_just_catch = false;
1032+
}
1033+
1034+
try {
1035+
/**
1036+
* Fires after the template enhancement output buffer has been finalized.
1037+
*
1038+
* This happens immediately before the template enhancement output buffer is flushed. No output may be printed
1039+
* at this action; prior to PHP 8.5, the output will be silently omitted, whereas afterward a deprecation notice
1040+
* will be emitted. Nevertheless, HTTP headers may be sent, which makes this action complimentary to the
1041+
* {@see 'send_headers'} action, in which headers may be sent before the template has started rendering. In
1042+
* contrast, this `wp_finalized_template_enhancement_output_buffer` action is the possible point at which HTTP
1043+
* headers can be sent. This action does not fire if the "template enhancement output buffer" was not started.
1044+
* This output buffer is automatically started if this action is added before
1045+
* {@see wp_start_template_enhancement_output_buffer()} runs at the {@see 'wp_before_include_template'} action
1046+
* with priority 1000. Before this point, the output buffer will also be started automatically if there was a
1047+
* {@see 'wp_template_enhancement_output_buffer'} filter added, or if the
1048+
* {@see 'wp_should_output_buffer_template_for_enhancement'} filter is made to return `true`.
1049+
*
1050+
* Important: Because this action fires inside an output buffer callback (i.e. display handler), any callbacks
1051+
* added to the action must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal
1052+
* error: "Cannot use output buffering in output buffering display handlers."
1053+
*
1054+
* @since 6.9.0
1055+
*
1056+
* @param string $output Finalized output buffer.
1057+
*/
1058+
do_action( 'wp_finalized_template_enhancement_output_buffer', $filtered_output );
1059+
} catch ( Throwable $throwable ) {
1060+
// Emit to the error log as a warning not as an error to prevent halting execution.
1061+
$did_just_catch = true;
1062+
trigger_error(
1063+
sprintf(
1064+
/* translators: %s is the class name */
1065+
__( 'Uncaught "%s" thrown:' ),
1066+
get_class( $throwable )
1067+
) . ' ' . $throwable->getMessage(),
1068+
E_USER_WARNING
1069+
);
1070+
$did_just_catch = false;
1071+
}
1072+
1073+
// Append any errors to be displayed before returning flushing the buffer.
1074+
if ( $original_display_errors && 'stderr' !== $original_display_errors ) {
1075+
foreach ( $error_log as $error ) {
1076+
switch ( $error['level'] ) {
1077+
case E_USER_NOTICE:
1078+
$type = 'Notice';
1079+
break;
1080+
case E_USER_DEPRECATED:
1081+
$type = 'Deprecated';
1082+
break;
1083+
case E_USER_WARNING:
1084+
$type = 'Warning';
1085+
break;
1086+
default:
1087+
$type = 'Error';
1088+
}
1089+
1090+
if ( ini_get( 'html_errors' ) ) {
1091+
/*
1092+
* Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1478>.
1093+
* The self-closing tags are a vestige of the XHTML past!
1094+
*/
1095+
$format = "%s<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%s</b><br />\n%s";
1096+
} else {
1097+
// Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1492>.
1098+
$format = "%s\n%s: %s in %s on line %s\n%s";
1099+
}
1100+
$filtered_output .= sprintf(
1101+
$format,
1102+
ini_get( 'error_prepend_string' ),
1103+
$type,
1104+
$error['message'],
1105+
$error['file'],
1106+
$error['line'],
1107+
ini_get( 'error_append_string' )
1108+
);
1109+
}
1110+
1111+
ini_set( 'display_errors', $original_display_errors );
1112+
}
1113+
1114+
restore_error_handler();
10121115

10131116
return $filtered_output;
10141117
}

0 commit comments

Comments
 (0)