From 6ea6972c87a8bc134dbedf1c81e5f1641fbaebf6 Mon Sep 17 00:00:00 2001 From: Tonya Mork Date: Wed, 6 Sep 2023 22:06:26 +0000 Subject: [PATCH] General: Introduce wp_trigger_error(). Introduces `wp_trigger_error()` as a wrapper around PHP's native `trigger_error()`. As a wrapper, it's lean and not opinionated about the message. It accepts an E_USER family error level, meaning it is not limited to only notices. Where `_doing_it_wrong()` intends to loudly alert developers "Hey you're doing it wrong - fix it", `wp_trigger_error()` is not opinionated and does not add wording. Rather, it passes the given message to `trigger_error()`. `wp_trigger_error()` is meant for every `trigger_error()` instance. It can be used: * in `_doing_it_wrong()` and each `_deprecated_*()` function. * for PHP 8.x deprecations. * for PHP error parity. * for less severe "doing it wrong" instance that do not require bailing out. * when a component or extension is not available on the server * for instances where it's not clear if a plugin's or theme's code is the root cause. * and more. Technical details: * Does not trigger the error if `WP_DEBUG` is not `true`. * Includes `wp_trigger_error_run` action to allow hooking in for backtracing and deeper debug. * Accepts an E_USER error level, but defaults to `E_USER_NOTICE`. * Requires a function name, though can be an empty string. As the output message generated by `trigger_error()` references the file and line number where it was invoked, passing the function's name provides more information where the error/warning/notice/deprecation happened. It's intended to help with debug. * A WordPress version number is not included. * As messages can appear in the browser, the message is escaped using `esc_html()`. As noted in [https://www.php.net/manual/en/function.trigger-error.php the PHP manual]: "HTML entities in message are not escaped. Use htmlentities() on the message if the error is to be displayed in a browser." References: * [https://www.php.net/manual/en/function.trigger-error.php PHP manual for `trigger_error()`]. * [https://www.php.net/manual/en/errorfunc.constants.php E_USER constants (error level) in the PHP manual]. Props azaozz, hellofromTonya, flixos90, costdev, peterwilsoncc, oglekler, mukesh27. See #57686. git-svn-id: https://develop.svn.wordpress.org/trunk@56530 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 46 ++++++++ .../tests/functions/wpTriggerError.php | 101 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 tests/phpunit/tests/functions/wpTriggerError.php diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 44728a2e41..835fa7190e 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -6039,6 +6039,52 @@ function _doing_it_wrong( $function_name, $message, $version ) { } } +/** + * Generates a user-level error/warning/notice/deprecation message. + * + * Generates the message when `WP_DEBUG` is true. + * + * @since 6.4.0 + * + * @param string $function_name The function that triggered the error. + * @param string $message The message explaining the error. + * @param int $error_level Optional. The designated error type for this error. + * Only works with E_USER family of constants. Default E_USER_NOTICE. + */ +function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) { + + // Bail out if WP_DEBUG is not turned on. + if ( ! WP_DEBUG ) { + return; + } + + /** + * Fires when the given function triggers a user-level error/warning/notice/deprecation message. + * + * Can be used for debug backtracking. + * + * @since 6.4.0 + * + * @param string $function_name The function that was called. + * @param string $message A message explaining what has been done incorrectly. + * @param int $error_level The designated error type for this error. + */ + do_action( 'wp_trigger_error_run', $function_name, $message, $error_level ); + + if ( ! empty( $function_name ) ) { + $message = sprintf( '%s(): %s', $function_name, $message ); + } + + /* + * If the message appears in the browser, then it needs to be escaped. + * Note the warning in the `trigger_error()` PHP manual. + * @link https://www.php.net/manual/en/function.trigger-error.php + */ + $message = esc_html( $message ); + + trigger_error( $message, $error_level ); +} + /** * Determines whether the server is running an earlier than 1.5.0 version of lighttpd. * diff --git a/tests/phpunit/tests/functions/wpTriggerError.php b/tests/phpunit/tests/functions/wpTriggerError.php new file mode 100644 index 0000000000..40582bc24a --- /dev/null +++ b/tests/phpunit/tests/functions/wpTriggerError.php @@ -0,0 +1,101 @@ +expectError(); + $this->expectErrorMessage( $expected_message ); + + wp_trigger_error( $function_name, $message, E_USER_ERROR ); + } + + /** + * @ticket 57686 + * + * @dataProvider data_should_trigger_error + * + * @param string $function_name The function name to test. + * @param string $message The message to test. + * @param string $expected_message The expected error message. + */ + public function test_should_trigger_warning( $function_name, $message, $expected_message ) { + $this->expectWarning(); + $this->expectWarningMessage( $expected_message ); + + wp_trigger_error( $function_name, $message, E_USER_WARNING ); + } + + /** + * @ticket 57686 + * + * @dataProvider data_should_trigger_error + * + * @param string $function_name The function name to test. + * @param string $message The message to test. + * @param string $expected_message The expected error message. + */ + public function test_should_trigger_notice( $function_name, $message, $expected_message ) { + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + wp_trigger_error( $function_name, $message ); + } + + /** + * @ticket 57686 + * + * @dataProvider data_should_trigger_error + * + * @param string $function_name The function name to test. + * @param string $message The message to test. + * @param string $expected_message The expected error message. + */ + public function test_should_trigger_deprecation( $function_name, $message, $expected_message ) { + $this->expectDeprecation(); + $this->expectDeprecationMessage( $expected_message ); + + wp_trigger_error( $function_name, $message, E_USER_DEPRECATED ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_trigger_error() { + return array( + 'function name and message are given' => array( + 'function_name' => 'some_function', + 'message' => 'expected the function name and message', + 'expected_message' => 'some_function(): expected the function name and message', + ), + 'message is given' => array( + 'function_name' => '', + 'message' => 'expect only the message', + 'expected_message' => 'expect only the message', + ), + 'function name is given' => array( + 'function_name' => 'some_function', + 'message' => '', + 'expected_message' => 'some_function(): ', + ), + ); + } +}