From df2882f842630bf24ee3b84c14655c53330d0414 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Thu, 1 Oct 2026 13:45:27 +0100 Subject: [PATCH 1/4] fix: don't flag get_posts() when suppress_filters is false The sniff warned on every call to get_posts(), wp_get_recent_posts() and get_children() without reading the arguments, so code that already followed the advice in the message still got the warning. Calls where the args are a variable or the value is not a literal false still warn, as the sniff cannot know what they hold. Fixes #899. --- .../Functions/RestrictedFunctionsSniff.php | 64 ++++++++++++++++++- .../Functions/RestrictedFunctionsUnitTest.inc | 18 ++++++ .../Functions/RestrictedFunctionsUnitTest.php | 7 ++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index bff5ce8d..b10ffe31 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -10,6 +10,10 @@ namespace WordPressVIPMinimum\Sniffs\Functions; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Utils\Arrays; +use PHPCSUtils\Utils\GetTokensAsString; +use PHPCSUtils\Utils\PassedParameters; +use PHPCSUtils\Utils\TextStrings; use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff; /** @@ -254,7 +258,6 @@ public function getGroups() { 'setcookie', ], ], - // @todo Introduce a sniff specific to get_posts() that checks for suppress_filters=>false being supplied. 'get_posts' => [ 'type' => 'warning', 'message' => '%s() is uncached unless the "suppress_filters" parameter is set to false. If the suppress_filter parameter is set to false this can be safely ignored. More Info: https://docs.wpvip.com/technical-references/caching/uncached-functions/.', @@ -326,4 +329,63 @@ public function is_targetted_token( $stackPtr ) { return $this->tokens[ $prevPrev ]['code'] === T_VARIABLE && isset( $this->groups[ $this->tokens[ $stackPtr ]['content'] ]['object_var'][ $this->tokens[ $prevPrev ]['content'] ] ); } + + /** + * Process a matched token. + * + * This differs to the parent class method that it overrides, by not flagging calls to + * the `get_posts` group of functions which set `suppress_filters` to `false`. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + if ( $group_name === 'get_posts' && $this->sets_suppress_filters_to_false( $stackPtr ) ) { + return; + } + + parent::process_matched_token( $stackPtr, $group_name, $matched_content ); + } + + /** + * Check whether a function call passes an array of arguments which sets `suppress_filters` to `false`. + * + * @param int $stackPtr The position of the function call name in the stack. + * + * @return bool + */ + private function sets_suppress_filters_to_false( $stackPtr ) { + $args_param = PassedParameters::getParameter( $this->phpcsFile, $stackPtr, 1, 'args' ); + if ( $args_param === false ) { + return false; + } + + $array_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $args_param['start'], $args_param['end'] + 1, true ); + if ( $array_ptr === false || Arrays::getOpenClose( $this->phpcsFile, $array_ptr ) === false ) { + return false; + } + + $is_false = false; + foreach ( PassedParameters::getParameters( $this->phpcsFile, $array_ptr ) as $item ) { + $arrow = Arrays::getDoubleArrowPtr( $this->phpcsFile, $item['start'], $item['end'] ); + if ( $arrow === false ) { + if ( strpos( GetTokensAsString::noEmpties( $this->phpcsFile, $item['start'], $item['end'] ), '...' ) === 0 ) { + // An unpacked array can override an earlier key. + $is_false = false; + } + continue; + } + + $key = TextStrings::stripQuotes( GetTokensAsString::noEmpties( $this->phpcsFile, $item['start'], $arrow - 1 ) ); + if ( $key === 'suppress_filters' ) { + $is_false = strtolower( GetTokensAsString::noEmpties( $this->phpcsFile, $arrow + 1, $item['end'] ) ) === 'false'; + } + } + + return $is_false; + } } diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc index 4a3f57b4..ba0fecff 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc @@ -246,3 +246,21 @@ $wp_rewrite /*comment*/ -> /*comment*/ flush_rules(); // Error. $wp_rewrite?->flush_rules(); // Error. array_walk($roles, add_role(...)); // Error. PHP 8.1 first class callable. + +// No warning when the args array sets suppress_filters to false. See #899. +get_posts( [ 'suppress_filters' => false ] ); // Ok. +get_posts( array( 'post_type' => 'post', 'suppress_filters' => FALSE ) ); // Ok. +wp_get_recent_posts( [ 'suppress_filters' => false ], OBJECT ); // Ok. +get_children( args: [ "suppress_filters" => false ] ); // Ok. +$post_ids = get_posts( [ + 'post_type' => [ 'post' ], + 'meta_key' => 'foobar', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key + 'suppress_filters' => false, +] ); // Ok. +get_posts( [ 'suppress_filters' => true ] ); // Warning. +get_posts( [ 'post_type' => 'post' ] ); // Warning. +get_posts( $args ); // Warning. +get_posts( [ 'suppress_filters' => $suppress ] ); // Warning. +get_posts( [ 'suppress_filters' => false, 'suppress_filters' => true ] ); // Warning - the last duplicate key wins. +get_posts( [ 'suppress_filters' => false, ...$args ] ); // Warning - the unpacked array can override the key. +get_posts( [ 'meta_query' => [ 'suppress_filters' => false ] ] ); // Warning - nested key. diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php index 1c727fac..d6252cdd 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php @@ -124,6 +124,13 @@ public function getWarningList() { 138 => 1, 139 => 1, 208 => 1, + 260 => 1, + 261 => 1, + 262 => 1, + 263 => 1, + 264 => 1, + 265 => 1, + 266 => 1, ]; } } From 97c3391ee87590cd2072ce6bed3e0adfc046dbe0 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 2 Oct 2026 15:44:41 +0100 Subject: [PATCH 2/4] fix: keep flagging get_children() when suppress_filters is false Both rulesets reuse the get_posts_get_children code for the no-LIMIT query that get_children() runs by default. Exempting it removed that error under WordPressVIPMinimum, and the warning under WordPress-VIP-Go. The ruleset tests now cover the call, which shifts the expected line numbers after it. --- WordPress-VIP-Go/ruleset-test.inc | 1 + WordPress-VIP-Go/ruleset-test.php | 164 +++++++++--------- .../Functions/RestrictedFunctionsSniff.php | 8 +- .../Functions/RestrictedFunctionsUnitTest.inc | 3 +- .../Functions/RestrictedFunctionsUnitTest.php | 1 + WordPressVIPMinimum/ruleset-test.inc | 1 + WordPressVIPMinimum/ruleset-test.php | 130 +++++++------- 7 files changed, 162 insertions(+), 146 deletions(-) diff --git a/WordPress-VIP-Go/ruleset-test.inc b/WordPress-VIP-Go/ruleset-test.inc index 5a17d238..ab99a21b 100644 --- a/WordPress-VIP-Go/ruleset-test.inc +++ b/WordPress-VIP-Go/ruleset-test.inc @@ -137,6 +137,7 @@ get_next_post_link(); // Warning. // WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_children get_child(); // Ok. get_children(); // Warning + Message. +get_children( [ 'suppress_filters' => false ] ); // Warning + Message. // WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_posts get_posts(); // Warning. diff --git a/WordPress-VIP-Go/ruleset-test.php b/WordPress-VIP-Go/ruleset-test.php index 6f929fbb..fb1d792f 100644 --- a/WordPress-VIP-Go/ruleset-test.php +++ b/WordPress-VIP-Go/ruleset-test.php @@ -20,33 +20,32 @@ 56 => 1, 72 => 1, 83 => 1, - 165 => 1, - 180 => 1, + 166 => 1, 181 => 1, - 187 => 1, + 182 => 1, 188 => 1, - 252 => 1, - 255 => 1, + 189 => 1, + 253 => 1, 256 => 1, - 258 => 1, + 257 => 1, 259 => 1, - 318 => 1, - 329 => 1, - 334 => 1, - 337 => 1, - 341 => 1, + 260 => 1, + 319 => 1, + 330 => 1, + 335 => 1, + 338 => 1, 342 => 1, - 350 => 1, + 343 => 1, 351 => 1, 352 => 1, 353 => 1, 354 => 1, 355 => 1, - 357 => 1, + 356 => 1, 358 => 1, 359 => 1, 360 => 1, - 362 => 1, + 361 => 1, 363 => 1, 364 => 1, 365 => 1, @@ -75,26 +74,26 @@ 388 => 1, 389 => 1, 390 => 1, - 409 => 1, + 391 => 1, 410 => 1, 411 => 1, 412 => 1, 413 => 1, 414 => 1, 415 => 1, - 431 => 1, - 441 => 1, - 462 => 1, - 466 => 1, - 468 => 1, - 470 => 1, - 475 => 1, - 477 => 1, - 483 => 1, - 489 => 1, - 497 => 1, - 510 => 1, - 514 => 1, + 416 => 1, + 432 => 1, + 442 => 1, + 463 => 1, + 467 => 1, + 469 => 1, + 471 => 1, + 476 => 1, + 478 => 1, + 484 => 1, + 490 => 1, + 498 => 1, + 511 => 1, 515 => 1, 516 => 1, 517 => 1, @@ -105,16 +104,17 @@ 522 => 1, 523 => 1, 524 => 1, - 528 => 1, - 530 => 1, - 548 => 1, - 563 => 1, - 567 => 1, + 525 => 1, + 529 => 1, + 531 => 1, + 549 => 1, + 564 => 1, 568 => 1, 569 => 1, 570 => 1, - 575 => 1, - 577 => 1, + 571 => 1, + 576 => 1, + 578 => 1, ], 'warnings' => [ 7 => 1, @@ -155,51 +155,51 @@ 130 => 1, 131 => 1, 139 => 1, - 142 => 1, - 146 => 1, - 150 => 1, - 154 => 1, - 157 => 1, - 161 => 1, - 169 => 1, - 174 => 1, + 140 => 1, + 143 => 1, + 147 => 1, + 151 => 1, + 155 => 1, + 158 => 1, + 162 => 1, + 170 => 1, 175 => 1, 176 => 1, 177 => 1, - 191 => 1, + 178 => 1, 192 => 1, - 195 => 1, + 193 => 1, 196 => 1, - 199 => 1, + 197 => 1, 200 => 1, 201 => 1, - 204 => 1, + 202 => 1, 205 => 1, 206 => 1, 207 => 1, 208 => 1, - 212 => 1, - 221 => 1, - 223 => 1, - 225 => 1, - 228 => 1, + 209 => 1, + 213 => 1, + 222 => 1, + 224 => 1, + 226 => 1, 229 => 1, 230 => 1, - 235 => 1, + 231 => 1, 236 => 1, 237 => 1, - 245 => 1, + 238 => 1, 246 => 1, 247 => 1, - 265 => 1, - 269 => 1, - 273 => 1, - 322 => 1, - 332 => 1, - 392 => 1, - 394 => 1, + 248 => 1, + 266 => 1, + 270 => 1, + 274 => 1, + 323 => 1, + 333 => 1, + 393 => 1, 395 => 1, - 398 => 1, + 396 => 1, 399 => 1, 400 => 1, 401 => 1, @@ -210,28 +210,29 @@ 406 => 1, 407 => 1, 408 => 1, - 416 => 1, + 409 => 1, 417 => 1, 418 => 1, 419 => 1, - 421 => 1, - 423 => 1, + 420 => 1, + 422 => 1, 424 => 1, 425 => 1, - 428 => 1, - 448 => 1, - 453 => 1, + 426 => 1, + 429 => 1, + 449 => 1, 454 => 1, 455 => 1, 456 => 1, - 505 => 1, + 457 => 1, 506 => 1, - 533 => 1, - 536 => 1, - 543 => 1, - 553 => 1, - 559 => 1, - 582 => 1, + 507 => 1, + 534 => 1, + 537 => 1, + 544 => 1, + 554 => 1, + 560 => 1, + 583 => 1, ], 'messages' => [ 7 => [ @@ -306,19 +307,22 @@ 139 => [ 'get_children() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. Please see: https://docs.wpvip.com/technical-references/caching/uncached-functions/', ], - 150 => [ + 140 => [ + 'get_children() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. Please see: https://docs.wpvip.com/technical-references/caching/uncached-functions/', + ], + 151 => [ 'url_to_postid() is uncached, please use wpcom_vip_url_to_postid() instead.', ], - 191 => [ + 192 => [ 'Scripts should be registered/enqueued via `wp_enqueue_script`. This can improve the site\'s performance due to script concatenation.', ], - 192 => [ + 193 => [ 'Scripts should be registered/enqueued via `wp_enqueue_script`. This can improve the site\'s performance due to script concatenation.', ], - 195 => [ + 196 => [ 'Stylesheets should be registered/enqueued via `wp_enqueue_style`. This can improve the site\'s performance due to styles concatenation.', ], - 196 => [ + 197 => [ 'Stylesheets should be registered/enqueued via `wp_enqueue_style`. This can improve the site\'s performance due to styles concatenation.', ], ], diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index b10ffe31..f0854eb3 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -334,7 +334,8 @@ public function is_targetted_token( $stackPtr ) { * Process a matched token. * * This differs to the parent class method that it overrides, by not flagging calls to - * the `get_posts` group of functions which set `suppress_filters` to `false`. + * `get_posts()` and `wp_get_recent_posts()` which set `suppress_filters` to `false`. + * `get_children()` is still flagged, as it also performs a no-LIMIT query by default. * * @param int $stackPtr The position of the current token in the stack. * @param string $group_name The name of the group which was matched. @@ -344,7 +345,10 @@ public function is_targetted_token( $stackPtr ) { * @return void */ public function process_matched_token( $stackPtr, $group_name, $matched_content ) { - if ( $group_name === 'get_posts' && $this->sets_suppress_filters_to_false( $stackPtr ) ) { + if ( $group_name === 'get_posts' + && $matched_content !== 'get_children' + && $this->sets_suppress_filters_to_false( $stackPtr ) + ) { return; } diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc index ba0fecff..8198f0a8 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc @@ -251,7 +251,7 @@ array_walk($roles, add_role(...)); // Error. PHP 8.1 first class callable. get_posts( [ 'suppress_filters' => false ] ); // Ok. get_posts( array( 'post_type' => 'post', 'suppress_filters' => FALSE ) ); // Ok. wp_get_recent_posts( [ 'suppress_filters' => false ], OBJECT ); // Ok. -get_children( args: [ "suppress_filters" => false ] ); // Ok. +get_posts( args: [ "suppress_filters" => false ] ); // Ok. $post_ids = get_posts( [ 'post_type' => [ 'post' ], 'meta_key' => 'foobar', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key @@ -264,3 +264,4 @@ get_posts( [ 'suppress_filters' => $suppress ] ); // Warning. get_posts( [ 'suppress_filters' => false, 'suppress_filters' => true ] ); // Warning - the last duplicate key wins. get_posts( [ 'suppress_filters' => false, ...$args ] ); // Warning - the unpacked array can override the key. get_posts( [ 'meta_query' => [ 'suppress_filters' => false ] ] ); // Warning - nested key. +get_children( args: [ "suppress_filters" => false ] ); // Warning - also performs a no-LIMIT query. diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php index d6252cdd..074b0b26 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php @@ -131,6 +131,7 @@ public function getWarningList() { 264 => 1, 265 => 1, 266 => 1, + 267 => 1, ]; } } diff --git a/WordPressVIPMinimum/ruleset-test.inc b/WordPressVIPMinimum/ruleset-test.inc index b47ae03c..388840e5 100644 --- a/WordPressVIPMinimum/ruleset-test.inc +++ b/WordPressVIPMinimum/ruleset-test.inc @@ -257,6 +257,7 @@ mt_rand(); // Warning. // WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_children get_children(); // Error + Message. +get_children( [ 'suppress_filters' => false ] ); // Error + Message. // VariableAnalysis.CodeAnalysis.VariableAnalysis function foo() { diff --git a/WordPressVIPMinimum/ruleset-test.php b/WordPressVIPMinimum/ruleset-test.php index 8fd42f54..c97cb561 100644 --- a/WordPressVIPMinimum/ruleset-test.php +++ b/WordPressVIPMinimum/ruleset-test.php @@ -73,27 +73,27 @@ 237 => 1, 238 => 1, 259 => 1, - 274 => 1, - 285 => 1, - 290 => 1, - 295 => 1, + 260 => 1, + 275 => 1, + 286 => 1, + 291 => 1, 296 => 1, - 299 => 1, - 303 => 1, + 297 => 1, + 300 => 1, 304 => 1, - 312 => 1, + 305 => 1, 313 => 1, 314 => 1, 315 => 1, 316 => 1, 317 => 1, - 319 => 1, + 318 => 1, 320 => 1, 321 => 1, 322 => 1, - 326 => 1, + 323 => 1, 327 => 1, - 333 => 1, + 328 => 1, 334 => 1, 335 => 1, 336 => 1, @@ -128,10 +128,10 @@ 365 => 1, 366 => 1, 367 => 1, - 369 => 1, - 371 => 1, + 368 => 1, + 370 => 1, 372 => 1, - 375 => 1, + 373 => 1, 376 => 1, 377 => 1, 378 => 1, @@ -149,20 +149,20 @@ 390 => 1, 391 => 1, 392 => 1, - 402 => 1, - 415 => 1, - 425 => 1, - 451 => 1, - 463 => 1, - 465 => 1, - 467 => 1, - 472 => 1, - 474 => 1, - 480 => 1, - 486 => 1, - 494 => 1, - 508 => 1, - 512 => 1, + 393 => 1, + 403 => 1, + 416 => 1, + 426 => 1, + 452 => 1, + 464 => 1, + 466 => 1, + 468 => 1, + 473 => 1, + 475 => 1, + 481 => 1, + 487 => 1, + 495 => 1, + 509 => 1, 513 => 1, 514 => 1, 515 => 1, @@ -173,29 +173,30 @@ 520 => 1, 521 => 1, 522 => 1, - 526 => 1, - 528 => 1, - 553 => 1, + 523 => 1, + 527 => 1, + 529 => 1, 554 => 1, - 557 => 1, - 572 => 1, + 555 => 1, + 558 => 1, 573 => 1, - 576 => 1, + 574 => 1, 577 => 1, 578 => 1, - 581 => 1, - 584 => 1, + 579 => 1, + 582 => 1, 585 => 1, 586 => 1, - 591 => 1, - 593 => 1, - 597 => 1, + 587 => 1, + 592 => 1, + 594 => 1, 598 => 1, 599 => 1, 600 => 1, - 615 => 1, - 617 => 1, - 624 => 1, + 601 => 1, + 616 => 1, + 618 => 1, + 625 => 1, ], 'warnings' => [ 32 => 1, @@ -244,51 +245,51 @@ 254 => 1, 255 => 1, 256 => 1, - 264 => 2, - 279 => 1, - 288 => 1, - 293 => 1, + 265 => 2, + 280 => 1, + 289 => 1, 294 => 1, - 324 => 1, - 396 => 1, + 295 => 1, + 325 => 1, 397 => 1, 398 => 1, 399 => 1, 400 => 1, 401 => 1, - 403 => 1, + 402 => 1, 404 => 1, 405 => 1, 406 => 1, 407 => 1, 408 => 1, - 411 => 1, + 409 => 1, 412 => 1, - 432 => 1, - 437 => 1, + 413 => 1, + 433 => 1, 438 => 1, 439 => 1, 440 => 1, 441 => 1, - 454 => 1, - 457 => 1, + 442 => 1, + 455 => 1, 458 => 1, 459 => 1, - 502 => 1, + 460 => 1, 503 => 1, - 507 => 1, - 531 => 1, + 504 => 1, + 508 => 1, 532 => 1, 533 => 1, 534 => 1, 535 => 1, - 538 => 1, - 541 => 1, - 548 => 1, - 562 => 1, - 568 => 1, - 592 => 1, - 621 => 1, + 536 => 1, + 539 => 1, + 542 => 1, + 549 => 1, + 563 => 1, + 569 => 1, + 593 => 1, + 622 => 1, ], 'messages' => [ 130 => [ @@ -306,6 +307,9 @@ 259 => [ '`get_children()` performs a no-LIMIT query by default, make sure to set a reasonable `posts_per_page`. `get_children()` will do a -1 query by default, a maximum of 100 should be used.', ], + 260 => [ + '`get_children()` performs a no-LIMIT query by default, make sure to set a reasonable `posts_per_page`. `get_children()` will do a -1 query by default, a maximum of 100 should be used.', + ], ], ]; From 043848151052307981c87c362540332616f0b974 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 2 Oct 2026 15:44:55 +0100 Subject: [PATCH 3/4] fix: reword the get_posts() warning for calls the sniff cannot read The sniff now skips calls it can see set suppress_filters to false, so the warning only reaches calls where it could not tell. "This can be safely ignored" no longer fits those without saying when. --- .../Sniffs/Functions/RestrictedFunctionsSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index f0854eb3..6499aac4 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -260,7 +260,7 @@ public function getGroups() { ], 'get_posts' => [ 'type' => 'warning', - 'message' => '%s() is uncached unless the "suppress_filters" parameter is set to false. If the suppress_filter parameter is set to false this can be safely ignored. More Info: https://docs.wpvip.com/technical-references/caching/uncached-functions/.', + 'message' => '%s() is uncached unless the "suppress_filters" parameter is set to false. If the parameter is set to false in a way this sniff cannot detect, such as via a variable, this can be safely ignored. More Info: https://docs.wpvip.com/technical-references/caching/uncached-functions/.', 'functions' => [ 'get_posts', 'wp_get_recent_posts', From 9bbdfb8af188e3ed95716abf8614352d9e66760b Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 2 Oct 2026 15:45:04 +0100 Subject: [PATCH 4/4] fix: read suppress_filters from tokens, not strings Three changes to how the sniff reads the args array: - The array has to be the whole argument, so `[ 'suppress_filters' => false ] ?: $args` warns again. - A lone `0` counts as well as `false`. WP_Query only checks that the value is falsy. - Unpacking is found by its token, without building a string from the whole item. --- .../Functions/RestrictedFunctionsSniff.php | 20 ++++++++++++++++--- .../Functions/RestrictedFunctionsUnitTest.inc | 4 ++++ .../Functions/RestrictedFunctionsUnitTest.php | 5 ++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index 6499aac4..8b08c23c 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -369,7 +369,15 @@ private function sets_suppress_filters_to_false( $stackPtr ) { } $array_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $args_param['start'], $args_param['end'] + 1, true ); - if ( $array_ptr === false || Arrays::getOpenClose( $this->phpcsFile, $array_ptr ) === false ) { + if ( $array_ptr === false ) { + return false; + } + + // The array has to be the whole argument. + $open_close = Arrays::getOpenClose( $this->phpcsFile, $array_ptr ); + if ( $open_close === false + || $open_close['closer'] !== $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $args_param['end'], $args_param['start'], true ) + ) { return false; } @@ -377,7 +385,8 @@ private function sets_suppress_filters_to_false( $stackPtr ) { foreach ( PassedParameters::getParameters( $this->phpcsFile, $array_ptr ) as $item ) { $arrow = Arrays::getDoubleArrowPtr( $this->phpcsFile, $item['start'], $item['end'] ); if ( $arrow === false ) { - if ( strpos( GetTokensAsString::noEmpties( $this->phpcsFile, $item['start'], $item['end'] ), '...' ) === 0 ) { + $first = $this->phpcsFile->findNext( Tokens::$emptyTokens, $item['start'], $item['end'] + 1, true ); + if ( $first !== false && $this->tokens[ $first ]['code'] === T_ELLIPSIS ) { // An unpacked array can override an earlier key. $is_false = false; } @@ -386,7 +395,12 @@ private function sets_suppress_filters_to_false( $stackPtr ) { $key = TextStrings::stripQuotes( GetTokensAsString::noEmpties( $this->phpcsFile, $item['start'], $arrow - 1 ) ); if ( $key === 'suppress_filters' ) { - $is_false = strtolower( GetTokensAsString::noEmpties( $this->phpcsFile, $arrow + 1, $item['end'] ) ) === 'false'; + // WP_Query only checks the value is falsy, so a lone `false` or `0` both count. + $value = $this->phpcsFile->findNext( Tokens::$emptyTokens, $arrow + 1, $item['end'] + 1, true ); + $is_false = $value !== false + && $this->phpcsFile->findNext( Tokens::$emptyTokens, $value + 1, $item['end'] + 1, true ) === false + && ( $this->tokens[ $value ]['code'] === T_FALSE + || ( $this->tokens[ $value ]['code'] === T_LNUMBER && $this->tokens[ $value ]['content'] === '0' ) ); } } diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc index 8198f0a8..37bb1f2c 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc @@ -252,6 +252,7 @@ get_posts( [ 'suppress_filters' => false ] ); // Ok. get_posts( array( 'post_type' => 'post', 'suppress_filters' => FALSE ) ); // Ok. wp_get_recent_posts( [ 'suppress_filters' => false ], OBJECT ); // Ok. get_posts( args: [ "suppress_filters" => false ] ); // Ok. +get_posts( [ 'suppress_filters' => 0 ] ); // Ok. $post_ids = get_posts( [ 'post_type' => [ 'post' ], 'meta_key' => 'foobar', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key @@ -264,4 +265,7 @@ get_posts( [ 'suppress_filters' => $suppress ] ); // Warning. get_posts( [ 'suppress_filters' => false, 'suppress_filters' => true ] ); // Warning - the last duplicate key wins. get_posts( [ 'suppress_filters' => false, ...$args ] ); // Warning - the unpacked array can override the key. get_posts( [ 'meta_query' => [ 'suppress_filters' => false ] ] ); // Warning - nested key. +get_posts( [ 'suppress_filters' => 1 ] ); // Warning. +get_posts( [ 'suppress_filters' => false || $suppress ] ); // Warning - not a lone false. +get_posts( [ 'suppress_filters' => false ] ?: $args ); // Warning - the array is not the whole argument. get_children( args: [ "suppress_filters" => false ] ); // Warning - also performs a no-LIMIT query. diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php index 074b0b26..c14f0e01 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php @@ -124,7 +124,6 @@ public function getWarningList() { 138 => 1, 139 => 1, 208 => 1, - 260 => 1, 261 => 1, 262 => 1, 263 => 1, @@ -132,6 +131,10 @@ public function getWarningList() { 265 => 1, 266 => 1, 267 => 1, + 268 => 1, + 269 => 1, + 270 => 1, + 271 => 1, ]; } }