Skip to content

fix(checks): reduce false positives for prepare() and saveHtml()#1348

Open
faisalahammad wants to merge 1 commit into
WordPress:trunkfrom
faisalahammad:fix/1333-reduce-false-positives
Open

fix(checks): reduce false positives for prepare() and saveHtml()#1348
faisalahammad wants to merge 1 commit into
WordPress:trunkfrom
faisalahammad:fix/1333-reduce-false-positives

Conversation

@faisalahammad

@faisalahammad faisalahammad commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Reduce two false positives in the PHPCS-based checks. Downgrade WordPress.DB.PreparedSQL.NotPrepared to a warning when a query is passed through $wpdb->prepare() with the spread operator, and stop reporting WordPress.Security.EscapeOutput.OutputNotEscaped for DOMDocument::saveHtml() and saveXML() calls.

Fixes #1333

Changes

includes/Checker/Checks/Plugin_Repo/Plugin_Review_PHPCS_Check.php

Before:

protected function add_result_message_for_file( Check_Result $result, $error, $message, $code, $file, $line = 0, $column = 0, string $docs = '', $severity = 5 ) {
    if ( 'PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound' === $code ) {
        $message .= ' ' . esc_html__( 'When your plugin is hosted on WordPress.org...', 'plugin-check' );
        $docs     = 'https://make.wordpress.org/core/2016/07/06/i18n-improvements-in-4-6/';
    }

    parent::add_result_message_for_file( $result, $error, $message, $code, $file, $line, $column, $docs, $severity );
}

After:

protected function add_result_message_for_file( Check_Result $result, $error, $message, $code, $file, $line = 0, $column = 0, string $docs = '', $severity = 5 ) {
    if ( 'PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound' === $code ) {
        $message .= ' ' . esc_html__( 'When your plugin is hosted on WordPress.org...', 'plugin-check' );
        $docs     = 'https://make.wordpress.org/core/2016/07/06/i18n-improvements-in-4-6/';
    }

    if ( 'WordPress.DB.PreparedSQL.NotPrepared' === $code && $error ) {
        $error   = false;
        $message .= ' ' . esc_html__( 'If the query variable is already being passed through $wpdb->prepare(), this warning may be a false positive. You can suppress it by adding a phpcs:ignore comment.', 'plugin-check' );
        $docs    = 'https://developer.wordpress.org/reference/classes/wpdb/prepare/';
    }

    parent::add_result_message_for_file( $result, $error, $message, $code, $file, $line, $column, $docs, $severity );
}

Why: PHPCS cannot follow a variable through $wpdb->prepare( $query, ...$args ), so the query is flagged as not prepared even when it is. Downgrading to a warning keeps the suggestion visible without blocking plugin submissions.

includes/Checker/Checks/Security/Late_Escaping_Check.php

Before: The OutputNotEscaped case only assigned a docs URL.

After: Adds a new helper line_calls_save_dom_method() that reads the source line for the reported error. If the line contains saveHtml( or saveXML(, the message is downgraded to a warning and pointed at the PHP manual page.

Why: DOMDocument::saveHtml() and saveXML() return safe HTML/XML built through the DOM API, so no further escaping is needed. Looking at the source line (rather than the PHPCS message, which only contains the variable name like $dom) is the reliable way to detect this.

Testing

Ran the full PHPUnit suite: 477 tests, 1353 assertions, all pass. Added two new test methods and one new test data plugin.

Test 1: Prepared SQL downgrade

  1. Add test plugin with $wpdb->get_results( $wpdb->prepare( $query, ...$args ) )
  2. Run Plugin Check
  3. Verify WordPress.DB.PreparedSQL.NotPrepared is a warning, not an error

Result: Works as expected.

Test 2: saveHtml() suppression

  1. Add test plugin with echo $dom->saveHtml();
  2. Run Plugin Check
  3. Verify no EscapeOutput.OutputNotEscaped reported for that line

Result: Works as expected.

Test 3: Normal escape detection still works

  1. Add test plugin with echo $test; where $test is a raw string
  2. Verify the error is still reported

Result: Works as expected.

Build

plugin-check-fix-1333.zip available for manual testing.
Install via WP Admin → Plugins → Upload Plugin.

Open WordPress Playground Preview

- Downgrade WordPress.DB.PreparedSQL.NotPrepared from error to warning
  when a variable is passed through $wpdb->prepare(), since PHPCS
  cannot trace the chain with the spread operator.
- Suppress WordPress.Security.EscapeOutput.OutputNotEscaped for
  DOMDocument::saveHtml() and saveXML() calls, since the output is
  already safe HTML/XML built through the DOM API.
- Add PHPUnit tests and test data for both fixes.

Fixes WordPress#1333
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

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.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @Tsjippy.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: Tsjippy.

Co-authored-by: faisalahammad <faisalahammad@git.wordpress.org>

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

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.

Reduce false positives: incorrect warning about $wpdb->prepare

1 participant