Skip to content

Conversation

@westonruter
Copy link
Member

@westonruter westonruter commented Oct 16, 2025

When display_errors is enabled, this temporarily turns off that setting and then adds an error handler to capture errors while applying wp_template_enhancement_output_buffer filters or doing the wp_finalized_template_enhancement_output_buffer action. After hooks have fired, it then appends any captured errors to the buffer. This fixes an issue where errors emitted during an output buffer callback are suppressed from the output which even though a site may have WP_DEBUG_DISPLAY enabled. In PHP 8.5, a deprecation notice is shown but the emitted errors are not displayed.

When an error is emitted in the wp_finalized_template_enhancement_output_buffer action, the error will be sent to the error log; it won't ever be printed (even if display_errors is enabled) because the output has been finalized.

Trac ticket: https://core.trac.wordpress.org/ticket/64108

Follow-up to r61111 and r61088.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter westonruter changed the title Capture errors emitted while applying wp_template_enhancement_output_buffer filters Handle errors emitted while applying wp_template_enhancement_output_buffer filters doing the wp_finalized_template_enhancement_output_buffer action Nov 1, 2025
@westonruter westonruter marked this pull request as ready for review November 1, 2025 20:12
@github-actions
Copy link

github-actions bot commented Nov 1, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter, dmsnell.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter
Copy link
Member Author

Here's example plugin code to test this:

add_filter(
	'wp_template_enhancement_output_buffer',
	static function ( $buffer ) {
		trigger_error( 'Oh no, a notice during filter!', E_USER_NOTICE );
		trigger_error( 'Oh no, a deprecation during filter!', E_USER_DEPRECATED );
		trigger_error( 'Oh no, a warning during filter!', E_USER_WARNING );
		_doing_it_wrong( __FUNCTION__, 'Why did you do this in a filter??', '0.1' );
		$buffer .= "<p>Filtered Successfully!</p>";
		return $buffer;
	},
	10
);
add_action(
	'wp_finalized_template_enhancement_output_buffer',
	static function () {
		trigger_error( 'Oh no, a notice during action!', E_USER_NOTICE );
		trigger_error( 'Oh no, a deprecation during action!', E_USER_DEPRECATED );
		trigger_error( 'Oh no, a warning during action!', E_USER_WARNING );
		_doing_it_wrong( __FUNCTION__, 'Why did you do this in an action??', '0.1' );
	},
	10
);

if ( isset( $_GET['emit_error_during_filter'] ) ) {
	add_filter(
		'wp_template_enhancement_output_buffer',
		static function ( $buffer ) {
			@trigger_error( 'Oh no, an error during filter!', E_USER_ERROR );
		},
		100
	);
}
if ( isset( $_GET['emit_exception_during_filter'] ) ) {
	add_filter(
		'wp_template_enhancement_output_buffer',
		static function () {
			throw new Exception( 'Oh no, an exception during filter!' );
		},
		100
	);
}

if ( isset( $_GET['emit_error_during_action'] ) ) {
	add_action(
		'wp_finalized_template_enhancement_output_buffer',
		static function () {
			@trigger_error( 'Oh no, an error during action!', E_USER_ERROR );
		},
		100
	);
}
if ( isset( $_GET['emit_exception_during_action'] ) ) {
	add_action(
		'wp_finalized_template_enhancement_output_buffer',
		static function () {
			throw new Exception( 'Oh no, an exception during action!' );
		},
		100
	);
}

When the plugin is active, accessing the site on trunk ends simply with:

Filtered Successfully!

No errors are displayed, unexpectedly. However, the error log does include:

[01-Nov-2025 21:03:58 UTC] PHP Notice:  Oh no, a notice during filter! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 9
[01-Nov-2025 21:03:58 UTC] PHP Deprecated:  Oh no, a deprecation during filter! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 10
[01-Nov-2025 21:03:58 UTC] PHP Warning:  Oh no, a warning during filter! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 11
[01-Nov-2025 21:03:58 UTC] PHP Notice:  Function {closure:/var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php:8} was called <strong>incorrectly</strong>. Why did you do this in a filter?? Please see <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/">Debugging in WordPress</a> for more information. (This message was added in version 0.1.) in /var/www/src/wp-includes/functions.php on line 6131
[01-Nov-2025 21:03:58 UTC] PHP Notice:  Oh no, a notice during action! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 21
[01-Nov-2025 21:03:58 UTC] PHP Deprecated:  Oh no, a deprecation during action! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 22
[01-Nov-2025 21:03:58 UTC] PHP Warning:  Oh no, a warning during action! in /var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php on line 23
[01-Nov-2025 21:03:58 UTC] PHP Notice:  Function {closure:/var/www/src/wp-content/plugins/emit-errors-during-template-enhancement-output-buffer-callback.php:20} was called <strong>incorrectly</strong>. Why did you do this in an action?? Please see <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/">Debugging in WordPress</a> for more information. (This message was added in version 0.1.) in /var/www/src/wp-includes/functions.php on line 6131

When checking out this branch, the error log entries remain unchanged, but the errors are now displayed (when display_errors is enabled):

image

When adding ?emit_exception_during_filter=1 to the URL, then you can see the modification added by the filter no longer applies ("Filtered Successfully!" is absent), but an error message is displayed:

image

If debug_display is off, then the no errors are printed.

Given that this is the template enhancement output buffer, it should gracefully degrade when an issue occurs in filter/action callbacks that fire during the execution of the output buffer display handler.

* @param string $output Original HTML template output buffer.
*/
$filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
} catch ( Exception $exception ) {
Copy link
Member

Choose a reason for hiding this comment

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

would you want to catch ( Throwable $throwable ) here to catch exceptions, errors, and all kinds of throwable things?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I suppose so. Although the error hander is already catching user errors. Would there be any other throwable thing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, I think I made the change you had in mind: 2d9bdd4

default:
$type = 'Error';
}
$format = "<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />";
Copy link
Member

Choose a reason for hiding this comment

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

can’t pass it up: the / on the <br /> is a benign misunderstanding and back-reading of XML into HTML. it’s ignored in HTML and its presence doesn’t turn HTML into “safe” XHTML. it’s only a concession to the widespread practice of adding these self-closing flags to HTML void elements that HTML5 didn’t codify them as actual syntax errors.

the concept didn’t exist until XML, which came after HTML.

just thoughts for anyone who sees them.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right. I'm only including it here because it's what PHP outputs. I tried to exactly replicate the native PHP error printing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I've made it explicit why those self-closing tags are being used in 2fe5542

Copy link
Member

Choose a reason for hiding this comment

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

no worries 😄

I have a personal mission to remove them from everywhere, but those aren’t blocking comments, just stubborn idealism.

}
$format = "<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />";
if ( ! ini_get( 'html_errors' ) ) {
$format = strip_tags( $format );
Copy link
Member

Choose a reason for hiding this comment

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

would we not want to use wp_strip_all_tags() here, or use some HTML API processor to get something as close to innerText as we can?

echo strip_tags( 'The <em>dog</em> walked &amp; barked.' );
// The dog walked &amp; barked.

or is the goal to try and prevent the possibility of throwing through some user-space bug?

of note, strip_tags() does not parse HTML particularly well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately wp_strip_all_tags() also does trim() at the end, so then it removes the initial newline.

The goal here is just to emulate what PHP outputs when the html_errors setting is turned off.

I'm only looking to remove the B and BR tags from the format string. It's not operating on the underlying error message being printed.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've eliminated this in 2fe5542

Copy link
Member

Choose a reason for hiding this comment

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

this reads clearer to me, and removes the complications of misparsing and leaving character references un-decoded.

}
);
$display_errors = ini_get( 'display_errors' );
if ( $display_errors ) {
Copy link
Member

Choose a reason for hiding this comment

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

if this is stderr is it a problem for the output buffering?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, interesting. I wasn't aware that was an option. I suppose it should be then be:

Suggested change
if ( $display_errors ) {
if ( $display_errors && 'stderr' !== $display_errors ) {

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I just tried this and it still is going to the frontend:

add_action( 'init', function () {
	ini_set( 'display_errors', 'stderr' );
} );

add_action( 'wp_footer', function () {
	trigger_error( 'You are not doing this right!!', E_USER_WARNING );
} );

Result:

...
		</svg>

		<br />
<b>Warning</b>:  You are not doing this right!! in <b>/var/www/src/wp-content/plugins/try-display-errors-stderr.php</b> on line <b>11</b><br />
<script type="importmap" id="wp-importmap">
...

This is in the wordpress-develop environment.

However, when I use PHP's built-in web server, when run this PHP coe:

ini_set( 'display_errors', 'stderr' );
trigger_error( 'You are not doing this right!!', E_USER_WARNING );

The result is that the error is not emitted to the frontend. It is sent to the terminal after the regular log entry:

[Sat Nov  1 19:59:25 2025] PHP Warning:  You are not doing this right!! in /private/tmp/try-stderr/try.php on line 4
[Sat Nov  1 19:59:25 2025] PHP Stack trace:
[Sat Nov  1 19:59:25 2025] PHP   1. {main}() /private/tmp/try-stderr/try.php:0
[Sat Nov  1 19:59:25 2025] PHP   2. trigger_error($message = 'You are not doing this right!!', $error_level = 512) /private/tmp/try-stderr/try.php:4
<br />
<font size='1'><table class='xdebug-error xe-warning' dir='ltr' border='1' cellspacing='0' cellpadding='1'>
<tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> Warning: You are not doing this right!! in /private/tmp/try-stderr/try.php on line <i>4</i></th></tr>
<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>
<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>
<tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0001</td><td bgcolor='#eeeeec' align='right'>359408</td><td bgcolor='#eeeeec'>{main}(  )</td><td title='/private/tmp/try-stderr/try.php' bgcolor='#eeeeec'>.../try.php<b>:</b>0</td></tr>
<tr><td bgcolor='#eeeeec' align='center'>2</td><td bgcolor='#eeeeec' align='center'>0.0001</td><td bgcolor='#eeeeec' align='right'>359816</td><td bgcolor='#eeeeec'><a href='http://www.php.net/function.trigger-error' target='_new'>trigger_error</a>( <span>$message = </span><span>&#39;You are not doing this right!!&#39;</span>, <span>$error_level = </span><span>512</span> )</td><td title='/private/tmp/try-stderr/try.php' bgcolor='#eeeeec'>.../try.php<b>:</b>4</td></tr>
</table></font>

So there is an inconsistency here.

It seems better safe to not emit in the case of stderr than to assume it is OK. So I've updated the logic in 5b13a71 to not append the errors when it is stderr.

Copy link
Member

Choose a reason for hiding this comment

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

there might be some difference when running as php -ddisplay_errors=stderr than when calling ini_set( 'display_errors', 'stderr' ) based on a note in the PHP manual page, but I think that’s only saying we don’t trap fatals because we’re catching them in the environment in which we’re throwing them, so probably doesn’t impact where the output is set. it wold not be the first time I’ve found the PHP manual page to be incomplete or incorrect in its assertions though.

I wish I understood the potential values here better. Seems like there are only two modes: STDOUT and STDERR, and you made the proper detection, because the INI value is either exactly stderr (in which case it uses the STDERR mode) or it’s anything else in which case it’s STDOUT.

https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L489-L518

westonruter and others added 3 commits November 1, 2025 19:49
westonruter and others added 2 commits November 1, 2025 20:06
@westonruter westonruter requested a review from dmsnell November 2, 2025 03:15
@westonruter
Copy link
Member Author

@dmsnell Are you happy to approve the changes here?


I had Gemini CLI do a review, and it gave me the green light:

The changes introduce robust error handling for the wp_template_enhancement_output_buffer filter and wp_finalized_template_enhancement_output_buffer action in src/wp-includes/template.php. This is a significant improvement for the stability of WordPress, preventing fatal errors from crashing the site due to misbehaving callbacks.

src/wp-includes/template.php:

  • Error Handling: The implementation of try...catch blocks around the filter and action calls is a solid approach to gracefully handle exceptions and errors.
  • Custom Error Handler: The custom error handler correctly captures various error types and allows for their display or logging without interrupting the execution flow, which is crucial for a robust system.
  • Docblock Updates: The docblocks for the filter and action have been updated to accurately reflect the new error handling behavior, including notes about output suppression and deprecation notices in newer PHP versions.
  • INI Configuration Management: The temporary modification and restoration of display_errors and the custom error handler are handled correctly, ensuring that the changes are localized to this function.
  • @phpstan-ignore: The @phpstan-ignore if.alwaysFalse comment is present. While the logic appears sound (the variable did_just_catch is indeed set in the catch block), it's worth noting that such ignores should be used judiciously. In this case, it seems to be a pragmatic solution for a specific static analysis warning.

tests/phpunit/tests/template.php:

  • Comprehensive Testing: The new test method test_wp_finalize_template_enhancement_output_buffer_with_errors_while_processing and its data provider demonstrate thorough testing of various error scenarios, including deprecated notices, warnings, and exceptions.
  • Adherence to Standards: The use of @ticket tags for referencing Trac tickets and @covers for indicating the covered function aligns with WordPress testing standards.
  • INI Configuration for Tests: The tests correctly manipulate INI settings to create controlled environments for testing error reporting, which is essential for reliable results.
  • Error Log Capture: The use of ini_set( 'error_log', $this->temp_filename() ) is an excellent practice for isolating and verifying error log output during tests.
  • @phpcs:ignore: The @phpcs:ignore comments are appropriately used in the test file to allow for specific code patterns necessary for testing error conditions.

Conclusion:

The changes are well-implemented, adhere to WordPress coding standards, and significantly improve the robustness of the template enhancement output buffering mechanism. The accompanying tests provide good coverage for the new error handling logic.

@westonruter
Copy link
Member Author

Since I believe all concerns have been addressed, I'm going ahead and committing this for beta3. Any additional feedback I can address over the next week.

pento pushed a commit that referenced this pull request Nov 4, 2025
…ation 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
@github-actions
Copy link

github-actions bot commented Nov 4, 2025

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 61120
GitHub commit: 5b03f1f

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions bot closed this Nov 4, 2025
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Nov 4, 2025
…ation 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 WordPress/wordpress-develop#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
github-actions bot pushed a commit to gilzow/wordpress-performance that referenced this pull request Nov 4, 2025
…ation 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 WordPress/wordpress-develop#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: https://core.svn.wordpress.org/trunk@60456 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Copy link
Member

@dmsnell dmsnell left a comment

Choose a reason for hiding this comment

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

@westonruter sorry I couldn’t attend to this earlier. thanks for working through it. there’s nothing I have to add here, but I can no longer approve it since it’s closed/merged.

Look forward to the improved error-handling though. 🙇‍♂️

Copy link

@mindctrl mindctrl left a comment

Choose a reason for hiding this comment

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

Late to submit but just a small phpdoc suggestion.

}

/**
* Data provider for data_provider_to_test_wp_finalize_template_enhancement_output_buffer_with_errors_while_processing.
Copy link

Choose a reason for hiding this comment

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

Suggested change
* Data provider for data_provider_to_test_wp_finalize_template_enhancement_output_buffer_with_errors_while_processing.
* Data provider for test_wp_finalize_template_enhancement_output_buffer_with_errors_while_processing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Committed in r61140

);
$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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants