diff --git a/docs/checks.md b/docs/checks.md index d52ba1f90..cc386eb52 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -36,3 +36,19 @@ | enqueued_styles_scope | performance | Checks whether any stylesheets are loaded on all pages, which is usually not desirable and can lead to performance issues. | [Learn more](https://developer.wordpress.org/plugins/) | | enqueued_scripts_scope | performance | Checks whether any scripts are loaded on all pages, which is usually not desirable and can lead to performance issues. | [Learn more](https://developer.wordpress.org/plugins/) | | non_blocking_scripts | performance | Checks whether scripts and styles are enqueued using a recommended loading strategy. | [Learn more](https://developer.wordpress.org/plugins/) | + +## Notes + +### Escaping widget wrapper output + +The `late_escaping` check expects all output to be escaped before it is sent to the browser. This also applies to widget display arguments such as `before_widget`, `after_widget`, `before_title`, and `after_title`. + +Classic widget examples often echo these values directly because themes provide the wrapper markup. When a plugin outputs them, use an escaping function that allows expected HTML, such as `wp_kses_post()`. Escape widget text according to the content it allows, such as `esc_html()` for plain text titles or `wp_kses_post()` for titles that intentionally allow limited markup: + +```php +echo wp_kses_post( $args['before_widget'] ); +echo wp_kses_post( $args['before_title'] ); +echo esc_html( $title ); +echo wp_kses_post( $args['after_title'] ); +echo wp_kses_post( $args['after_widget'] ); +``` diff --git a/tests/phpunit/testdata/plugins/test-plugin-late-escaping-without-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-late-escaping-without-errors/load.php index 428ebe81e..f38053547 100644 --- a/tests/phpunit/testdata/plugins/test-plugin-late-escaping-without-errors/load.php +++ b/tests/phpunit/testdata/plugins/test-plugin-late-escaping-without-errors/load.php @@ -20,3 +20,24 @@ */ esc_html_e( 'Hello World!', 'test-plugin-check' ); + +/** + * Outputs widget markup with escaped wrapper arguments. + * + * @param array $args Widget display arguments. + * @param array $instance Widget instance settings. + */ +function test_plugin_check_widget_output( $args, $instance ) { + $title = isset( $instance['title'] ) ? $instance['title'] : ''; + + echo wp_kses_post( $args['before_widget'] ); + + if ( '' !== $title ) { + echo wp_kses_post( $args['before_title'] ); + echo esc_html( $title ); + echo wp_kses_post( $args['after_title'] ); + } + + echo '

' . esc_html__( 'Widget content.', 'test-plugin-check' ) . '

'; + echo wp_kses_post( $args['after_widget'] ); +}