From 1feffcae0df4fddc32ee53311d2d64e069d8fc22 Mon Sep 17 00:00:00 2001 From: kevinfodness Date: Tue, 8 Sep 2020 15:52:02 -0400 Subject: [PATCH 001/129] Ignore return values when a filter callback is abstract --- .../Hooks/AlwaysReturnInFilterSniff.php | 9 +++++++++ .../Hooks/AlwaysReturnInFilterUnitTest.inc | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index f5ea5734..6b3689ca 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -175,6 +175,15 @@ private function processFunction( $stackPtr, $start = 0, $end = null ) { */ private function processFunctionBody( $stackPtr ) { + /** + * Stop if the constructor doesn't have a body, like when it is abstract. + * + * @see https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php#L87-L90 + */ + if ( false === isset( $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + return; + } + $argPtr = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_STRING, T_OPEN_PARENTHESIS ] ), $stackPtr + 1, diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc index 4f9d6d00..be74c9e3 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc @@ -172,3 +172,23 @@ class bad_example_class_short_array { // Error. } } +abstract class good_example_abstract_class { // Ok. + public function __construct() { + add_filter( 'good_example_class_filter', [ $this, 'class_filter' ] ); + } + + abstract public function class_filter( $param ); +} + +class good_example_abstract_class_implementation { // Ok. + public function class_filter( $param ) { + if ( 1 === 1 ) { + if ( 1 === 0 ) { + return 'whoops'; + } else { + return 'here!'; + } + } + return 'This is Okay'; + } +} From adccc63c628e9440244a9d2ed3500ef90f5a3517 Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Wed, 9 Sep 2020 08:17:54 +0100 Subject: [PATCH 002/129] Avoid yoda condition --- WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index 6b3689ca..14cbb55b 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -180,7 +180,7 @@ private function processFunctionBody( $stackPtr ) { * * @see https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php#L87-L90 */ - if ( false === isset( $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + if ( isset( $this->tokens[ $stackPtr ]['scope_closer'] ) === false ) { return; } From 015b7402d720aae3746611103867768ca09a52c4 Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Wed, 9 Sep 2020 09:22:12 +0100 Subject: [PATCH 003/129] DocBlock fix --- WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index 14cbb55b..c546693a 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -176,7 +176,7 @@ private function processFunction( $stackPtr, $start = 0, $end = null ) { private function processFunctionBody( $stackPtr ) { /** - * Stop if the constructor doesn't have a body, like when it is abstract. + * Stop if the function doesn't have a body, like when it is abstract. * * @see https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php#L87-L90 */ From b756e7074a2a03c2ecb0787cb3bce6903cd7478a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Sep 2020 15:48:00 +0200 Subject: [PATCH 004/129] Travis: retry composer install on failure The builds are failing a little too often for my taste on the below error. ``` [Composer\Downloader\TransportException] Peer fingerprint did not match ``` I'm prefixing the `composer install` commands now with `travis_retry` in an attempt to get round this problem. Ref: * https://docs.travis-ci.com/user/common-build-problems/#timeouts-installing-dependencies --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 65f73b2d..914d0082 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ jobs: - ./bin/php-lint # Add PHPCS locally for the XSD. - - composer require squizlabs/php_codesniffer + - travis_retry composer require squizlabs/php_codesniffer # Validate the XML files and check the code-style consistency of the XML files. - ./bin/xml-lint @@ -62,7 +62,7 @@ jobs: php: 7.4 env: PHPCS_BRANCH="dev-master" before_install: phpenv config-rm xdebug.ini || echo 'No xdebug config.' - install: composer install --no-suggest + install: travis_retry composer install --no-suggest script: # Run PHPCS against VIPCS. - ./bin/phpcs @@ -96,14 +96,14 @@ before_install: fi install: - - composer require squizlabs/php_codesniffer:"$PHPCS_BRANCH" --no-update --no-suggest --no-scripts + - travis_retry composer require squizlabs/php_codesniffer:"$PHPCS_BRANCH" --no-update --no-suggest --no-scripts - | if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then - # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform - # requirements to get PHPUnit 7.x to install on nightly. - composer install --ignore-platform-reqs --no-suggest + # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform + # requirements to get PHPUnit 7.x to install on nightly. + travis_retry composer install --ignore-platform-reqs --no-suggest else - composer install --no-suggest + travis_retry composer install --no-suggest fi script: From 8e4077d32b8aceb45cd645bb4e6074af05f101dd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Sep 2020 17:21:44 +0200 Subject: [PATCH 005/129] AlwaysReturnInFilter: update unit tests. * Adjust the "abstract method" test to: 1. Expect a warning. 2. Prevent the hook in getting confused with other functions in the same test file. * Remove the "abstract method implementation" test as it wasn't testing anything. 1. The sniff does not look for child classes, so wouldn't examine that code snippet anyway. Adding this functionality is not that useful either as in most cases, the child class will not be in the same file as the abstract parent class. 2. And even if the sniff did examine it, it would still not recognize it as a child class as the class doesn't `extend` the abstract. * Add a test for a typical case where declared functions do not have a scope opener/closer due to a tokenizer bug. PR squizlabs/PHP_CodeSniffer 3066 is open upstream to fix this. * Add a "live coding" test case. --- .../Hooks/AlwaysReturnInFilterUnitTest.inc | 29 ++++++++++--------- .../Hooks/AlwaysReturnInFilterUnitTest.php | 4 ++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc index be74c9e3..4d29e003 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc @@ -172,23 +172,26 @@ class bad_example_class_short_array { // Error. } } -abstract class good_example_abstract_class { // Ok. +abstract class warning_filter_points_to_abstract_method { public function __construct() { - add_filter( 'good_example_class_filter', [ $this, 'class_filter' ] ); + add_filter( 'good_example_abstract_class', [ $this, 'abstract_method' ] ); } - abstract public function class_filter( $param ); + abstract public function abstract_method( $param ); // Warning. } -class good_example_abstract_class_implementation { // Ok. - public function class_filter( $param ) { - if ( 1 === 1 ) { - if ( 1 === 0 ) { - return 'whoops'; - } else { - return 'here!'; - } - } - return 'This is Okay'; +class tokenizer_bug_test { + public function __construct() { + add_filter( 'tokenizer_bug', [ $this, 'tokenizer_bug' ] ); } + + public function tokenizer_bug( $param ): namespace\Foo {} // Ok (tokenizer bug in PHPCS < 3.5.7/3.6.0). } + +// Intentional parse error. This has to be the last test in the file! +class parse_error_test { + public function __construct() { + add_filter( 'parse_error', [ $this, 'parse_error' ] ); + } + + public function parse_error( $param ) // Ok, parse error ignored. diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php index 747e0b83..c47e881c 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php @@ -40,7 +40,9 @@ public function getErrorList() { * @return array => */ public function getWarningList() { - return []; + return [ + 180 => 1, + ]; } } From 4356274fd99676fa47a8efc0b36dfe497f5ee9e8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Sep 2020 17:33:20 +0200 Subject: [PATCH 006/129] AlwaysReturnInFilter: differentiate between abstract method and parse error This implements the suggestion made in https://github.com/Automattic/VIP-Coding-Standards/issues/580#issuecomment-689119006 If the method a filter callback points to is an abstract method, a warning will be thrown asking for manual inspection of the child class implementations of the abstract method. In case of parse or tokenizer error, the sniff will bow out and stay silent. --- .../Hooks/AlwaysReturnInFilterSniff.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index c546693a..2cda2587 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -175,12 +175,18 @@ private function processFunction( $stackPtr, $start = 0, $end = null ) { */ private function processFunctionBody( $stackPtr ) { - /** - * Stop if the function doesn't have a body, like when it is abstract. - * - * @see https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php#L87-L90 - */ - if ( isset( $this->tokens[ $stackPtr ]['scope_closer'] ) === false ) { + $filterName = $this->tokens[ $this->filterNamePtr ]['content']; + + $methodProps = $this->phpcsFile->getMethodProperties( $stackPtr ); + if ( $methodProps['is_abstract'] === true ) { + $message = 'The callback for the `%s` filter hook-in points to an abstract method. Please, make sure that all child class implementations of this abstract method always return a value.'; + $data = [ $filterName ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'AbstractMethod', $data ); + return; + } + + if ( isset( $this->tokens[ $stackPtr ]['scope_opener'], $this->tokens[ $stackPtr ]['scope_closer'] ) === false ) { + // Live coding, parse or tokenizer error. return; } @@ -198,8 +204,6 @@ private function processFunctionBody( $stackPtr ) { return; } - $filterName = $this->tokens[ $this->filterNamePtr ]['content']; - $functionBodyScopeStart = $this->tokens[ $stackPtr ]['scope_opener']; $functionBodyScopeEnd = $this->tokens[ $stackPtr ]['scope_closer']; From 6c190d2b698dace8e14542870d53b03033f8244e Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Sun, 13 Sep 2020 10:36:43 +0100 Subject: [PATCH 007/129] Update WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php Co-authored-by: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> --- WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index 2cda2587..718349df 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -179,7 +179,7 @@ private function processFunctionBody( $stackPtr ) { $methodProps = $this->phpcsFile->getMethodProperties( $stackPtr ); if ( $methodProps['is_abstract'] === true ) { - $message = 'The callback for the `%s` filter hook-in points to an abstract method. Please, make sure that all child class implementations of this abstract method always return a value.'; + $message = 'The callback for the `%s` filter hook-in points to an abstract method. Please ensure that child class implementations of this method always return a value.'; $data = [ $filterName ]; $this->phpcsFile->addWarning( $message, $stackPtr, 'AbstractMethod', $data ); return; From 12f2c9b027d988b0f71706057d7e6f67fcc06b6a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 13 Sep 2020 23:44:56 +0200 Subject: [PATCH 008/129] Composer: require the DealerDirect plugin As VIPCS requires two external standards, including the new `VariableAnalysis` dependency, let's require the DealerDirect plugin to make life easier on people who install via Composer. Includes updating the Readme to mention the `VariableAnalysis` standard, as well as mention that the DealerDirect plugin is now a project requirement. Note: I've widened the version constraints for the DealerDirect plugin to prevent conflicts with customer projects which already required the plugin, but potentially at a different version. The version constraints now set cover all released versions which support external standards properly. --- README.md | 11 ++++------- composer.json | 5 +---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 064eabe4..942cd6c9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project contains two rulesets: These rulesets contain only the rules which are considered to be "errors" and "warnings" according to the [WordPress VIP Go documentation](https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/) -The rulesets use rules from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (WPCS) project. +The rulesets use rules from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (WPCS) project, as well as the [VariableAnalysis](https://github.com/sirbrillig/phpcs-variable-analysis) standard. Go to https://wpvip.com/documentation/phpcs-review-feedback/ to learn about why violations are flagged as errors vs warnings and what the levels mean. @@ -18,20 +18,17 @@ Go to https://wpvip.com/documentation/phpcs-review-feedback/ to learn about why * PHP 5.4+ * [PHPCS 3.5.5+](https://github.com/squizlabs/PHP_CodeSniffer/releases) * [WPCS 2.3.0+](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/releases) +* [VariableAnalysis 2.8.3+](https://github.com/sirbrillig/phpcs-variable-analysis/releases) ## Installation `composer require automattic/vipwpcs`, or `composer g require automattic/vipwpcs` if installing globally. -This will install the latest compatible versions of PHPCS and WPCS. +This will install the latest compatible versions of PHPCS, WPCS and VariableAnalysis and register the external standards with PHP_CodeSniffer. Please refer to the [installation instructions for installing PHP_CodeSniffer for WordPress.com VIP](https://wpvip.com/documentation/how-to-install-php-code-sniffer-for-wordpress-com-vip/) for more details. -We recommend the [PHP_CodeSniffer Standards Composer Installer Plugin](https://github.com/Dealerdirect/phpcodesniffer-composer-installer), which handles the registration of all of the installed standards, so there is no need to set the `installed_paths` config value manually, for single or multiple standards. - -Alternatively, you should register the standard to PHPCS by appending the VIPCS directory to the end of the installed paths. e.g. - -`phpcs --config-set installed_paths /path/to/wpcsstandard,path/to/vipcsstandard` +As of VIPCS version 2.3.0, there is no need to `require` the [PHP_CodeSniffer Standards Composer Installer Plugin](https://github.com/Dealerdirect/phpcodesniffer-composer-installer) anymore as it is now a requirement of VIPCS itself. ## Contribution diff --git a/composer.json b/composer.json index 9c52020c..9f6a5daf 100644 --- a/composer.json +++ b/composer.json @@ -16,18 +16,15 @@ ], "require": { "php": ">=5.4", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7", "sirbrillig/phpcs-variable-analysis": "^2.8.3", "squizlabs/php_codesniffer": "^3.5.5", "wp-coding-standards/wpcs": "^2.3" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", "phpcompatibility/php-compatibility": "^9", "phpunit/phpunit": "^4 || ^5 || ^6 || ^7" }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will manage the PHPCS 'installed_paths' automatically." - }, "scripts": { "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", "ruleset": "bin/ruleset-tests", From 0faf223f7f7701072e08b32fed447aea7038f91a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 14 Sep 2020 15:56:11 +0200 Subject: [PATCH 009/129] Performance: more selective sniffing for efficiency The `register()` method tells PHPCS which tokens a sniff is listening for. A number of sniffs were currently registering the [`Tokens::$functionNameTokens`](https://github.com/squizlabs/PHP_CodeSniffer/blob/9cce6859a595b3fa77fc823d9b0d451951dcf9d2/src/Util/Tokens.php#L570-L583) array. In all cases, these sniffs were only looking for specific function calls, and in most cases, a "name" check is the first thing in the sniff code or else such a check was done at a later point in the code. This makes using the `Tokens::$functionNameTokens` array highly inefficient, as the _only_ token within that array which could have the "name" the sniff is looking for is `T_STRING`. The other eleven (!) tokens registered would **never** match and would never trigger an error, but would be passed to the sniff even so. This commit replaces the use of the `Tokens::$functionNameTokens` array in nearly all places in the code base with `T_STRING` for higher accuracy and more efficient sniffing. Note: in a future PR, once PHPCSUtils is used, these sniffs should be adjusted to take the [PHP 8.0 namespaced identifier name tokens](https://wiki.php.net/rfc/namespaced_names_as_token) into account, but that is for later. --- .../Sniffs/Compatibility/ZoninatorSniff.php | 2 +- .../Sniffs/Functions/CheckReturnValueSniff.php | 12 ++++++------ .../Sniffs/Hooks/AlwaysReturnInFilterSniff.php | 4 ++-- .../Sniffs/Hooks/PreGetPostsSniff.php | 6 +++--- .../Sniffs/Performance/CacheValueOverrideSniff.php | 6 +----- .../Sniffs/Performance/FetchingRemoteDataSniff.php | 2 +- .../Performance/TaxonomyMetaInOptionsSniff.php | 2 +- .../Sniffs/Security/ExitAfterRedirectSniff.php | 2 +- .../Sniffs/Security/ProperEscapingFunctionSniff.php | 2 +- .../Sniffs/Security/StaticStrreplaceSniff.php | 2 +- 10 files changed, 18 insertions(+), 22 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php b/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php index 7d18a7f5..1dae2b96 100644 --- a/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php +++ b/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php @@ -22,7 +22,7 @@ class ZoninatorSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } diff --git a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php index a95c2acf..9c7c31db 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php @@ -60,7 +60,7 @@ class CheckReturnValueSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -86,7 +86,7 @@ public function process_token( $stackPtr ) { */ private function isFunctionCall( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['code'], Tokens::$functionNameTokens, true ) === false ) { + if ( $this->tokens[ $stackPtr ]['code'] !== T_STRING ) { return false; } @@ -162,14 +162,14 @@ public function findDirectFunctionCalls( $stackPtr ) { $closeBracket = $this->tokens[ $openBracket ]['parenthesis_closer']; $startNext = $openBracket + 1; - $next = $this->phpcsFile->findNext( Tokens::$functionNameTokens, $startNext, $closeBracket, false, null, true ); + $next = $this->phpcsFile->findNext( T_STRING, $startNext, $closeBracket, false, null, true ); while ( $next ) { if ( in_array( $this->tokens[ $next ]['content'], $this->catch[ $functionName ], true ) === true ) { $message = "`%s`'s return type must be checked before calling `%s` using that value."; $data = [ $this->tokens[ $next ]['content'], $functionName ]; $this->phpcsFile->addError( $message, $next, 'DirectFunctionCall', $data ); } - $next = $this->phpcsFile->findNext( Tokens::$functionNameTokens, $next + 1, $closeBracket, false, null, true ); + $next = $this->phpcsFile->findNext( T_STRING, $next + 1, $closeBracket, false, null, true ); } } @@ -269,7 +269,7 @@ public function findNonCheckedVariables( $stackPtr ) { foreach ( $callees as $callee ) { $notFunctionsCallee = array_key_exists( $callee, $this->notFunctions ) ? (array) $this->notFunctions[ $callee ] : []; // Check whether the found token is one of the function calls (or foreach call) we are interested in. - if ( in_array( $this->tokens[ $nextFunctionCallWithVariable ]['code'], array_merge( Tokens::$functionNameTokens, $notFunctionsCallee ), true ) === true + if ( in_array( $this->tokens[ $nextFunctionCallWithVariable ]['code'], array_merge( [ T_STRING ], $notFunctionsCallee ), true ) === true && $this->tokens[ $nextFunctionCallWithVariable ]['content'] === $callee ) { $this->addNonCheckedVariableError( $nextFunctionCallWithVariable, $variableName, $callee ); @@ -278,7 +278,7 @@ public function findNonCheckedVariables( $stackPtr ) { $search = array_merge( Tokens::$emptyTokens, [ T_EQUAL ] ); $next = $this->phpcsFile->findNext( $search, $nextVariableOccurrence + 1, null, true ); - if ( in_array( $this->tokens[ $next ]['code'], Tokens::$functionNameTokens, true ) === true + if ( $this->tokens[ $next ]['code'] === T_STRING && $this->tokens[ $next ]['content'] === $callee ) { $this->addNonCheckedVariableError( $next, $variableName, $callee ); diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index f5ea5734..20ec8ebf 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -30,7 +30,7 @@ class AlwaysReturnInFilterSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -130,7 +130,7 @@ private function processString( $stackPtr, $start = 0, $end = null ) { $callbackFunctionName = substr( $this->tokens[ $stackPtr ]['content'], 1, -1 ); $callbackFunctionPtr = $this->phpcsFile->findNext( - Tokens::$functionNameTokens, + T_STRING, $start, $end, false, diff --git a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php index eb1dce4e..a7d4ed6e 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php @@ -25,7 +25,7 @@ class PreGetPostsSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -120,7 +120,7 @@ private function processString( $stackPtr ) { $callbackFunctionName = substr( $this->tokens[ $stackPtr ]['content'], 1, -1 ); $callbackFunctionPtr = $this->phpcsFile->findNext( - Tokens::$functionNameTokens, + T_STRING, 0, null, false, @@ -394,7 +394,7 @@ private function isWPQueryMethodCall( $stackPtr, $method = null ) { true ); - return $next && in_array( $this->tokens[ $next ]['code'], Tokens::$functionNameTokens, true ) === true && $method === $this->tokens[ $next ]['content']; + return $next && $this->tokens[ $next ]['code'] === T_STRING && $method === $this->tokens[ $next ]['content']; } /** diff --git a/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php b/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php index 96696437..806bebef 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php @@ -29,7 +29,7 @@ class CacheValueOverrideSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } @@ -97,10 +97,6 @@ public function process_token( $stackPtr ) { */ private function isFunctionCall( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['code'], Tokens::$functionNameTokens, true ) === false ) { - return false; - } - // Find the next non-empty token. $openBracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true ); diff --git a/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php b/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php index 62d1d090..e3fc4330 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php @@ -24,7 +24,7 @@ class FetchingRemoteDataSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php index 071dc641..c9c815be 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php @@ -51,7 +51,7 @@ class TaxonomyMetaInOptionsSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php b/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php index 5d76976a..dfacc425 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php @@ -24,7 +24,7 @@ class ExitAfterRedirectSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index f94abdf2..fe973be1 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -35,7 +35,7 @@ class ProperEscapingFunctionSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php b/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php index 09159a07..3d57edcc 100644 --- a/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php @@ -24,7 +24,7 @@ class StaticStrreplaceSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** From 09f3700bfcdd1768f9caace82a8fc4f50d67e294 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 05:04:36 +0200 Subject: [PATCH 010/129] Hooks/AlwaysReturnInFilter: adjust expectations for a test ... as an upstream PR containing a bug fix for the tokenizer issue this test documents has been merged. Ref: * squizlabc/PHP_CodeSniffer 3066 --- .../Tests/Hooks/AlwaysReturnInFilterUnitTest.inc | 2 +- .../Tests/Hooks/AlwaysReturnInFilterUnitTest.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc index 4d29e003..77930dd5 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc @@ -185,7 +185,7 @@ class tokenizer_bug_test { add_filter( 'tokenizer_bug', [ $this, 'tokenizer_bug' ] ); } - public function tokenizer_bug( $param ): namespace\Foo {} // Ok (tokenizer bug in PHPCS < 3.5.7/3.6.0). + public function tokenizer_bug( $param ): namespace\Foo {} // Ok (tokenizer bug PHPCS < 3.5.7) / Error in PHPCS >= 3.5.7. } // Intentional parse error. This has to be the last test in the file! diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php index c47e881c..8cc2cef4 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php @@ -7,7 +7,9 @@ namespace WordPressVIPMinimum\Tests\Hooks; +use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the Hooks/AlwaysReturn sniff. * @@ -31,6 +33,7 @@ public function getErrorList() { 105 => 1, 129 => 1, 163 => 1, + 188 => version_compare( Config::VERSION, '3.5.7', '>=' ) ? 1 : 0, ]; } From 44e592f86ba20c7d4c908d3c3474b5e1cbce063c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:05:24 +0200 Subject: [PATCH 011/129] Files/IncludingNonPHPFile: minor efficiency fix [1] * Move the extensions against which checks are being run to private properties. * Removes the `.` from the part in the regex which is being remembered. Note: The `.` before the extension is still required, it is just no longer _remembered_. * Changes the extension checks from using the relatively expensive `in_array()` to the lightweight `isset()`. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index ba1735c1..cba9925d 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -21,6 +21,28 @@ */ class IncludingNonPHPFileSniff extends Sniff { + /** + * File extensions used for PHP files. + * + * Files with these extensions are allowed to be `include`d. + * + * @var array Key is the extension, value is irrelevant. + */ + private $php_extensions = [ + 'php' => true, + 'inc' => true, + ]; + + /** + * File extensions used for SVG and CSS files. + * + * @var array Key is the extension, value is irrelevant. + */ + private $svg_css_extensions = [ + 'css' => true, + 'svg' => true, + ]; + /** * Returns an array of tokens this test wants to listen for. * @@ -49,14 +71,14 @@ public function process_token( $stackPtr ) { $stringWithoutEnclosingQuotationMarks = $this->tokens[ $curStackPtr ]['content']; } - $isFileName = preg_match( '/.*(\.[a-z]{2,})$/i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); + $isFileName = preg_match( '/.*\.([a-z]{2,})$/i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); if ( $isFileName === false || $isFileName === 0 ) { continue; } $extension = $regexMatches[1]; - if ( in_array( $extension, [ '.php', '.inc' ], true ) === true ) { + if ( isset( $this->php_extensions[ $extension ] ) === true ) { return; } @@ -64,7 +86,7 @@ public function process_token( $stackPtr ) { $data = [ $this->tokens[ $stackPtr ]['content'] ]; $code = 'IncludingNonPHPFile'; - if ( in_array( $extension, [ '.svg', '.css' ], true ) === true ) { + if ( isset( $this->svg_css_extensions[ $extension ] ) === true ) { // Be more specific for SVG and CSS files. $message = 'Local SVG and CSS files should be loaded via `file_get_contents` rather than via `%s`.'; $code = 'IncludingSVGCSSFile'; From 1f3143ef6e3603a19b8a87fec538ee16665beeef Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:07:37 +0200 Subject: [PATCH 012/129] Files/IncludingNonPHPFile: minor efficiency fix [2] The regex will work just the same without the `.*`. --- WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index cba9925d..7e134582 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -71,7 +71,7 @@ public function process_token( $stackPtr ) { $stringWithoutEnclosingQuotationMarks = $this->tokens[ $curStackPtr ]['content']; } - $isFileName = preg_match( '/.*\.([a-z]{2,})$/i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); + $isFileName = preg_match( '`\.([a-z]{2,})$`i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); if ( $isFileName === false || $isFileName === 0 ) { continue; From 81f23d8dc8f0140dd7f39912c93c630574098c10 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:11:51 +0200 Subject: [PATCH 013/129] Files/IncludingNonPHPFile: improve the existing unit tests * The existing unit tests only tested against `require_once` and `include`, while the sniff looks for all variations. The newly added unit tests make sure that all four variations have at least one positive/negative test associated with it. * Add some capitalization variations to the `include|require[_once]` keywords. --- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index ae1e4cf0..d32c71bc 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -2,25 +2,25 @@ require_once __DIR__ . "/my_file.php"; // OK. -require_once "my_file.php"; // OK. +require "my_file.php"; // OK. -include( __DIR__ . "/my_file.php" ); // OK. +include_once( __DIR__ . "/my_file.php" ); // OK. include ( MY_CONSTANT . "my_file.php" ); // OK. require_once ( MY_CONSTANT . "my_file.php" ); // OK. -include( locate_template('index-loop.php') ); // OK. +Include( locate_template('index-loop.php') ); // OK. require_once __DIR__ . "/my_file.svg"; // NOK. -require_once "my_file.svg"; // NOK. +Require_Once "my_file.svg"; // NOK. include( __DIR__ . "/my_file.svg" ); // NOK. include ( MY_CONSTANT . "my_file.svg" ); // NOK. -require_once ( MY_CONSTANT . "my_file.svg" ); // NOK. +require ( MY_CONSTANT . "my_file.svg" ); // NOK. include( locate_template('index-loop.svg') ); // NOK. @@ -28,7 +28,7 @@ require_once __DIR__ . "/my_file.css"; // NOK. require_once "my_file.css"; // NOK. -include( __DIR__ . "/my_file.css" ); // NOK. +include_once( __DIR__ . "/my_file.css" ); // NOK. include ( MY_CONSTANT . "my_file.css" ); // NOK. @@ -36,7 +36,7 @@ require_once ( MY_CONSTANT . "my_file.css" ); // NOK. include( locate_template('index-loop.css') ); // NOK. -require_once __DIR__ . "/my_file.csv"; // NOK. +REQUIRE_ONCE __DIR__ . "/my_file.csv"; // NOK. require_once "my_file.inc"; // OK. @@ -50,4 +50,4 @@ include( locate_template('index-loop.csv') ); // NOK. echo file_get_contents( 'index-loop.svg' ); // XSS OK. -echo file_get_contents( 'index-loop.css' ); // XSS OK. \ No newline at end of file +echo file_get_contents( 'index-loop.css' ); // XSS OK. From 0d8b86b9b1e22c36829bd62c0a95898bece23ea8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:18:20 +0200 Subject: [PATCH 014/129] Files/IncludingNonPHPFile: improve the error message * Make sure the `include|require[_once]` keyword is always referred to in lower case in the error message. * Make the error message more informative by displaying the text snippet which triggered the error. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 7e134582..52241741 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -82,13 +82,16 @@ public function process_token( $stackPtr ) { return; } - $message = 'Local non-PHP file should be loaded via `file_get_contents` rather than via `%s`.'; - $data = [ $this->tokens[ $stackPtr ]['content'] ]; + $message = 'Local non-PHP file should be loaded via `file_get_contents` rather than via `%s`. Found: %s'; + $data = [ + strtolower( $this->tokens[ $stackPtr ]['content'] ), + $this->tokens[ $curStackPtr ]['content'], + ]; $code = 'IncludingNonPHPFile'; if ( isset( $this->svg_css_extensions[ $extension ] ) === true ) { // Be more specific for SVG and CSS files. - $message = 'Local SVG and CSS files should be loaded via `file_get_contents` rather than via `%s`.'; + $message = 'Local SVG and CSS files should be loaded via `file_get_contents` rather than via `%s`. Found: %s'; $code = 'IncludingSVGCSSFile'; } From 8693ccb3b46742b787e8970e6779ce36b9c593cd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:25:40 +0200 Subject: [PATCH 015/129] Files/IncludingNonPHPFile: bug fix [1] - case sensitivity of file extension The regex used to retrieve an extension, would retrieve it case-insensitively, but the previous `in_array()` check (now `isset()`), checked the extension in a case-sensitive manner. Whether the file extension is used in upper, lower or mixed case, does not matter to the include as long as a matching file is found, so the sniff should check the extension in a case insensitive manner. As things were, a file name like `file.PHP` would trigger a false positive for the `IncludingNonPHPFile` error and a file called `file.Css` would incorrectly throw the `IncludingNonPHPFile` error instead of the `IncludingSVGCSSFile` error which it should have thrown. Fixed now. Tested by adjusting some of the existing unit tests. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 2 +- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 52241741..6db56e01 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -77,7 +77,7 @@ public function process_token( $stackPtr ) { continue; } - $extension = $regexMatches[1]; + $extension = strtolower( $regexMatches[1] ); if ( isset( $this->php_extensions[ $extension ] ) === true ) { return; } diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index d32c71bc..aa7a62ff 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -6,13 +6,13 @@ require "my_file.php"; // OK. include_once( __DIR__ . "/my_file.php" ); // OK. -include ( MY_CONSTANT . "my_file.php" ); // OK. +include ( MY_CONSTANT . "my_file.INC" ); // OK. require_once ( MY_CONSTANT . "my_file.php" ); // OK. -Include( locate_template('index-loop.php') ); // OK. +Include( locate_template('index-loop.PHP') ); // OK. -require_once __DIR__ . "/my_file.svg"; // NOK. +require_once __DIR__ . "/my_file.SVG"; // NOK. Require_Once "my_file.svg"; // NOK. @@ -24,7 +24,7 @@ require ( MY_CONSTANT . "my_file.svg" ); // NOK. include( locate_template('index-loop.svg') ); // NOK. -require_once __DIR__ . "/my_file.css"; // NOK. +require_once __DIR__ . "/my_file.CSS"; // NOK. require_once "my_file.css"; // NOK. @@ -34,13 +34,13 @@ include ( MY_CONSTANT . "my_file.css" ); // NOK. require_once ( MY_CONSTANT . "my_file.css" ); // NOK. -include( locate_template('index-loop.css') ); // NOK. +include( locate_template('index-loop.Css') ); // NOK. REQUIRE_ONCE __DIR__ . "/my_file.csv"; // NOK. require_once "my_file.inc"; // OK. -include( __DIR__ . "/my_file.csv" ); // NOK. +include( __DIR__ . "/my_file.CSV" ); // NOK. include ( MY_CONSTANT . "my_file.csv" ); // NOK. From 939d4279a6805658986d51d0a3d1d3abb2cd25b8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 00:10:33 +0200 Subject: [PATCH 016/129] Files/IncludingNonPHPFile: bug fix [2] - allow for "phar" file extension PHAR (PHP Archive) files should be regarded as PHP files for the purposes of this sniff. Includes unit tests. Fixes 478 --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 5 +++-- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 6db56e01..b93f4f95 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -29,8 +29,9 @@ class IncludingNonPHPFileSniff extends Sniff { * @var array Key is the extension, value is irrelevant. */ private $php_extensions = [ - 'php' => true, - 'inc' => true, + 'php' => true, + 'inc' => true, + 'phar' => true, ]; /** diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index aa7a62ff..df9f8687 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -51,3 +51,7 @@ include( locate_template('index-loop.csv') ); // NOK. echo file_get_contents( 'index-loop.svg' ); // XSS OK. echo file_get_contents( 'index-loop.css' ); // XSS OK. + +include_once 'path/to/geoip.phar'; // OK. + +require dirname(__DIR__) . '/externals/aws-sdk.phar'; // OK. From 69949ba2476acaee909fbe979b973b7603d4b380 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 01:51:32 +0200 Subject: [PATCH 017/129] Files/IncludingNonPHPFile: bug fix [3] - extensions in interpolated strings The sniff looks for `Tokens::$stringTokens` when examining the statement. The `Tokens::$stringTokens` array contains two tokens: 1. `T_CONSTANT_ENCAPSED_STRING` - these are either single or double quoted text strings without variables within them. 2. `T_DOUBLE_QUOTED_STRING` - these are double quoted text strings with interpolated variables in the text string. As things were, the quotes would be stripped off the first, but not off the contents of `T_DOUBLE_QUOTED_STRING`-type tokens. As the regex matches extensions at the very end of the text string and doesn't allow for a double quote at the end, this effectively meant that text in double quoted strings would never be recognized as containing a non-PHP extension. (false negative) Includes unit tests. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 6 +----- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 6 ++++++ .../Tests/Files/IncludingNonPHPFileUnitTest.php | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index b93f4f95..0e0dbf38 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -66,11 +66,7 @@ public function process_token( $stackPtr ) { while ( $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ) !== false ) { $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ); - if ( $this->tokens[ $curStackPtr ]['code'] === T_CONSTANT_ENCAPSED_STRING ) { - $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); - } else { - $stringWithoutEnclosingQuotationMarks = $this->tokens[ $curStackPtr ]['content']; - } + $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); $isFileName = preg_match( '`\.([a-z]{2,})$`i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index df9f8687..5e64a41e 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -55,3 +55,9 @@ echo file_get_contents( 'index-loop.css' ); // XSS OK. include_once 'path/to/geoip.phar'; // OK. require dirname(__DIR__) . '/externals/aws-sdk.phar'; // OK. + +require "$path/$file.inc"; // OK. + +require "$path/$file.css"; // NOK. + +include_once $path . '/' . "$file.js"; // NOK. diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php index ad139cc6..ef07949b 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php @@ -41,6 +41,8 @@ public function getErrorList() { 45 => 1, 47 => 1, 49 => 1, + 61 => 1, + 63 => 1, ]; } From b04c016af6135ba7b09e0568a2f902de6bd91904 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 02:05:34 +0200 Subject: [PATCH 018/129] Files/IncludingNonPHPFile: bug fix [4] - fix bleed through /end of statement An `include`/`require` statement can be used within template files to include another template. In that case, the statement can be ended by a PHP close tag without there ever being a semi-colon. Even though the `findNext()` function call indicated that it only wants to look "locally", the `findNext()` method itself does not take a PHP close tag into account when determining whether the statement has ended. This could be considered a bug upstream, but that's another matter. In practice, this meant that when an `include|require[_once]` statement ended on a PHP close tag, the `findNext()` would continue looking for text string tokens and could report an error on a text string which is unrelated to the `include|require[_once]` statement. (false positive). Determining the end of the statement properly before calling `findNext()` fixes this, however, the `findEndOfStatement()` method will return the last non-whitespace token in a statement, which for statements nested in brackets can be a token which is _part_ of the statement. As the `findNext()` method does not look at the `$end` token, we need to `+1` the result of `findEndOfStatement()` to make sure the whole statement is considered. Includes unit tests. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 7 ++++--- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 15 ++++++++++++++- .../Tests/Files/IncludingNonPHPFileUnitTest.php | 2 ++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 0e0dbf38..39b80a18 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -62,9 +62,10 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { - $curStackPtr = $stackPtr; - while ( $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ) !== false ) { - $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ); + $curStackPtr = $stackPtr; + $end_of_statement = $this->phpcsFile->findEndOfStatement( $stackPtr ); + while ( $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1 ) !== false ) { + $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1 ); $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index 5e64a41e..d85097bb 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -60,4 +60,17 @@ require "$path/$file.inc"; // OK. require "$path/$file.css"; // NOK. -include_once $path . '/' . "$file.js"; // NOK. +include_once $path . '/' . "$file.js" ?> + 1, 61 => 1, 63 => 1, + 69 => 1, + 72 => 1, ]; } From 8e713d7907f6a3d70df91fd459a4332e9ecb0633 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 02:32:26 +0200 Subject: [PATCH 019/129] Files/IncludingNonPHPFile: efficiency fix [3] To prevent an assignment in condition, the same function call was executed twice. By changing the code around slightly and using a `do - while` loop instead of a `while`, the duplicate function call can be removed. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 39b80a18..39227cfa 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -64,8 +64,12 @@ public function register() { public function process_token( $stackPtr ) { $curStackPtr = $stackPtr; $end_of_statement = $this->phpcsFile->findEndOfStatement( $stackPtr ); - while ( $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1 ) !== false ) { - $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1 ); + + do { + $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1); + if ( $curStackPtr === false ) { + return; + } $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); @@ -94,7 +98,7 @@ public function process_token( $stackPtr ) { } $this->phpcsFile->addError( $message, $curStackPtr, $code, $data ); - } + } while ( $curStackPtr <= $end_of_statement ); } } From 3cc9dca43f1a2eac9e022dbd67607c40c3b50f5b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 02:59:46 +0200 Subject: [PATCH 020/129] Files/IncludingNonPHPFile: efficiency fix [4]/bug fix [5] - extension at end of statement An `include|require[_once]` statement can only include one (1) file and the file extension of that file will normally be at the _end_ of the statement. So far, the sniff would walk from the start of the statement to the end, while walking from the end of the statement to the start, will get us a result more quickly and more accurately. Includes unit tests. The first would be a false negative, the second a false positive without this fix. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 6 +++--- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 3 +++ .../Tests/Files/IncludingNonPHPFileUnitTest.php | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 39227cfa..e6b99b68 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -62,11 +62,11 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { - $curStackPtr = $stackPtr; $end_of_statement = $this->phpcsFile->findEndOfStatement( $stackPtr ); + $curStackPtr = ( $end_of_statement + 1 ); do { - $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, $end_of_statement + 1); + $curStackPtr = $this->phpcsFile->findPrevious( Tokens::$stringTokens, $curStackPtr - 1, $stackPtr ); if ( $curStackPtr === false ) { return; } @@ -98,7 +98,7 @@ public function process_token( $stackPtr ) { } $this->phpcsFile->addError( $message, $curStackPtr, $code, $data ); - } while ( $curStackPtr <= $end_of_statement ); + } while ( $curStackPtr > $stackPtr ); } } diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index d85097bb..1cccee42 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -71,6 +71,9 @@ if ((include 'vars.svg') && $imported_var === 'foo') {} // NOK. require ( dirname(__FILE__) . '/path' ) . 'concatafterparentheses.php'; // OK. require ( dirname(__FILE__) . '/path' ) . 'concatafterparentheses.py'; // NOK. +include_once '/src.css' . DIRECTORY_SEPARATOR . $subdir . '/filename.php'; // OK. +include_once '/src.php' . DIRECTORY_SEPARATOR . $subdir . '/filename.css'; // NOK. + // Live coding. // Intentional parse error. This has to be the last test in the file. include $path . 'filename.css diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php index 5902c598..ebb0075b 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php @@ -45,6 +45,7 @@ public function getErrorList() { 63 => 1, 69 => 1, 72 => 1, + 75 => 1, ]; } From 5f4de3ed4d5c8e8e19aaf96c69f5fe4dcf51f2ee Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 02:47:31 +0200 Subject: [PATCH 021/129] Files/IncludingNonPHPFile: efficiency fix [5]/bug fix [6] - one error per statement An `include|require[_once]` statement can only include one (1) file, so there should only ever be one file extension in the statement and by extension, only ever be one error for any particular `include|require[_once]` statement. Once a file extension has been found which generates an error, we can therefore stop sniffing that statement. This is similar to the sniff breaking off checking the statement once a text string with `.php` as a file extension is found, as was already done. Includes unit tests. --- WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php | 4 ++++ .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 2 ++ .../Tests/Files/IncludingNonPHPFileUnitTest.php | 1 + 3 files changed, 7 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index e6b99b68..bf51b2b2 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -98,6 +98,10 @@ public function process_token( $stackPtr ) { } $this->phpcsFile->addError( $message, $curStackPtr, $code, $data ); + + // Don't throw more than one error for any one statement. + return; + } while ( $curStackPtr > $stackPtr ); } diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index 1cccee42..f4bb68c3 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -74,6 +74,8 @@ require ( dirname(__FILE__) . '/path' ) . 'concatafterparentheses.py'; // NOK. include_once '/src.css' . DIRECTORY_SEPARATOR . $subdir . '/filename.php'; // OK. include_once '/src.php' . DIRECTORY_SEPARATOR . $subdir . '/filename.css'; // NOK. +include_once '/src.csv' . DIRECTORY_SEPARATOR . $subdir . '/filename.png'; // NOK. + // Live coding. // Intentional parse error. This has to be the last test in the file. include $path . 'filename.css diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php index ebb0075b..ff016f30 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php @@ -46,6 +46,7 @@ public function getErrorList() { 69 => 1, 72 => 1, 75 => 1, + 77 => 1, ]; } From 21138e17575e3e10185600b3ba5b52b83bcfd927 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 03:36:43 +0200 Subject: [PATCH 022/129] Files/IncludingNonPHPFile: add some additional unit tests ... to document the behaviour of the sniff for certain situations. --- .../Tests/Files/IncludingNonPHPFileUnitTest.inc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index f4bb68c3..b4226a25 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -76,6 +76,10 @@ include_once '/src.php' . DIRECTORY_SEPARATOR . $subdir . '/filename.css'; // NO include_once '/src.csv' . DIRECTORY_SEPARATOR . $subdir . '/filename.png'; // NOK. +include 'http://www.example.com/file.php?foo=1&bar=2'; // OK - this sniff is not about remote includes. + +require __DIR__ . '/' . $subdir . '/' . $filename . '.' . $extension; // OK - undetermined. + // Live coding. // Intentional parse error. This has to be the last test in the file. include $path . 'filename.css From d00943682dba0e8b32eef0ca290f3f1ce09da5a1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 15:05:46 +0200 Subject: [PATCH 023/129] Files/IncludingNonPHPFile: remove unused `use` statement --- WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index bf51b2b2..95dce91f 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -7,7 +7,6 @@ namespace WordPressVIPMinimum\Sniffs\Files; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; From 06fe6108847e67731d354e20ba308ff87be6aca0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 03:22:20 +0200 Subject: [PATCH 024/129] Files/IncludingNonPHPFile: minor simplification `preg_match()` will return `false` for an invalid regex, `0` for "no match" and `1` for a match as it only looks for one match - in contrast to `preg_match_all()`-. As the regex uses an "end of text string" anchor, using `preg_match()` is the right function, but that also means we can simplify the return value check a little. --- WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 95dce91f..2022d5d6 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -74,7 +74,7 @@ public function process_token( $stackPtr ) { $isFileName = preg_match( '`\.([a-z]{2,})$`i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); - if ( $isFileName === false || $isFileName === 0 ) { + if ( $isFileName !== 1 ) { continue; } From 1b4fccbf9dfc68263addd02f26d90e7d3fb2a564 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Sep 2020 00:11:37 +0200 Subject: [PATCH 025/129] Files/IncludingNonPHPFile: fix class docblocks The existing text was unrelated to the actual sniff. Probably legacy copy/paste. --- .../Sniffs/Files/IncludingNonPHPFileSniff.php | 6 ++---- .../Tests/Files/IncludingNonPHPFileUnitTest.php | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index 2022d5d6..de30c6e5 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -11,10 +11,9 @@ use PHP_CodeSniffer\Util\Tokens; /** - * WordPressVIPMinimum_Sniffs_Files_IncludingNonPHPFileSniff. + * Ensure that non-PHP files are included via `file_get_contents()` instead of using `include/require[_once]`. * - * Checks that __DIR__, dirname( __FILE__ ) or plugin_dir_path( __FILE__ ) - * is used when including or requiring files. + * This prevents potential PHP code embedded in those files from being automatically executed. * * @package VIPCS\WordPressVIPMinimum */ @@ -52,7 +51,6 @@ public function register() { return Tokens::$includeTokens; } - /** * Processes this test, when one of its tokens is encountered. * diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php index ff016f30..f16c6c2b 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.php @@ -8,8 +8,9 @@ namespace WordPressVIPMinimum\Tests\Files; use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** - * Unit test class for the IncludingFile sniff. + * Unit test class for the IncludingNonPHPFile sniff. * * @package VIPCS\WordPressVIPMinimum * From a3a38b2716d5ee695b4b25cb5571be767ac828c5 Mon Sep 17 00:00:00 2001 From: Rebecca Hum Date: Tue, 27 Aug 2019 15:49:19 -0600 Subject: [PATCH 026/129] ConstantStringSniff: Take into account namespace prefixing --- .../Sniffs/Constants/ConstantStringSniff.php | 8 +++++++- .../Tests/Constants/ConstantStringUnitTest.inc | 12 +++++++++--- .../Tests/Constants/ConstantStringUnitTest.php | 5 +++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php index 24a13635..461827bc 100644 --- a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php +++ b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php @@ -55,7 +55,13 @@ public function process_token( $stackPtr ) { return; } - $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); + $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); + $nextNextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); + + if ( T_NS_C === $this->tokens[ $nextToken ]['code'] && T_STRING_CONCAT === $this->tokens[ $nextNextToken ]['code'] ) { + // Namespacing being used, skip to next. + $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextNextToken + 1, null, true, null, true ); + } if ( $this->tokens[ $nextToken ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { $message = 'Constant name, as a string, should be used along with `%s()`.'; diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc index a330da69..5bace853 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc @@ -4,6 +4,12 @@ if ( ! defined( 'WPCOM_VIP' ) ) { // Okay. define( 'WPCOM_VIP', true ); // Okay. } -if ( ! defined( WPCOM_VIP ) ) { // NOK. - define( WPCOM_VIP ); // NOK. -} \ No newline at end of file +if ( ! defined( WPCOM_VIP ) ) { // Error. + define( WPCOM_VIP ); // Error. +} + +namespace Foo/Bar; +const REST_ALLOWED_META_PREFIXES = [ 'foo-', 'bar-', 'baz-' ]; +if ( defined( __NAMESPACE__ . '\REST_ALLOWED_META_PREFIXES' ) && in_array( 'foo-', REST_ALLOWED_META_PREFIXES, true ) ) { // Ok. + define( __NAMESPACE__ . REST_ALLOWED_META_PREFIXES ); // Error. +} diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php index 9b6b4225..b03d06d6 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php @@ -25,8 +25,9 @@ class ConstantStringUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return [ - 7 => 1, - 8 => 1, + 7 => 1, + 8 => 1, + 14 => 1, ]; } From 7c6dc07239bc708107f518ab277dc04b1ca87879 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Wed, 28 Aug 2019 08:34:10 -0600 Subject: [PATCH 027/129] Namespace should be backslash rather than forward slash on unit test Co-Authored-By: Juliette <663378+jrfnl@users.noreply.github.com> --- WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc index 5bace853..b07e5e7f 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc @@ -8,7 +8,7 @@ if ( ! defined( WPCOM_VIP ) ) { // Error. define( WPCOM_VIP ); // Error. } -namespace Foo/Bar; +namespace Foo\Bar; const REST_ALLOWED_META_PREFIXES = [ 'foo-', 'bar-', 'baz-' ]; if ( defined( __NAMESPACE__ . '\REST_ALLOWED_META_PREFIXES' ) && in_array( 'foo-', REST_ALLOWED_META_PREFIXES, true ) ) { // Ok. define( __NAMESPACE__ . REST_ALLOWED_META_PREFIXES ); // Error. From b01bca1c11fc907697b7daf67bdcdf237ff0b4e7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 25 Sep 2020 16:22:42 +0200 Subject: [PATCH 028/129] Constants/ConstantString: prevent more false positives The `define()` and `defined()` functions expect the name of a constant as a text string to be passed as the first parameter. This sniff is intended to guard against a coding mistake where the name of the constant would be passed as a_constant_. While passing the name of the constant as a constant _could_ be perfectly valid when that constant contains the name of another constant as a text string, more often than not, it will be a coding error. As things were, the sniff did not handle parameters build up using more than one token into account. It also did not handle valid situations, like passing the constant name as a text string via a variable to the functions, correctly. As outlined in https://github.com/Automattic/VIP-Coding-Standards/issues/422#issuecomment-698934220 (Proposal 1), this limits the sniff to _only_ trigger an error when a plain constant has been passed as the "constant name" parameter and ignore all other cases. Note: The current implementation of this fix, uses the WPCS `get_function_call_parameter()` method to retrieve the stack pointer to the start and end of the parameter. Once PHPCSUtils is in place, this function call should be replaced by using the PHPCSUtils version of that method. Fixes 422 Fixes 480 --- .../Sniffs/Constants/ConstantStringSniff.php | 26 ++++++++++++------- .../Constants/ConstantStringUnitTest.inc | 22 ++++++++++++++-- .../Constants/ConstantStringUnitTest.php | 5 ++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php index 461827bc..d6506c8f 100644 --- a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php +++ b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php @@ -55,20 +55,26 @@ public function process_token( $stackPtr ) { return; } - $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); - $nextNextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); - - if ( T_NS_C === $this->tokens[ $nextToken ]['code'] && T_STRING_CONCAT === $this->tokens[ $nextNextToken ]['code'] ) { - // Namespacing being used, skip to next. - $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextNextToken + 1, null, true, null, true ); + $param = $this->get_function_call_parameter( $stackPtr, 1 ); + if ( $param === false ) { + // Target parameter not found. + return; } - if ( $this->tokens[ $nextToken ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { - $message = 'Constant name, as a string, should be used along with `%s()`.'; - $data = [ $this->tokens[ $stackPtr ]['content'] ]; - $this->phpcsFile->addError( $message, $nextToken, 'NotCheckingConstantName', $data ); + $search = Tokens::$emptyTokens; + $search[ T_STRING ] = T_STRING; + + $has_only_tstring = $this->phpcsFile->findNext( $search, $param['start'], $param['end'] + 1, true ); + if ( $has_only_tstring !== false ) { + // Came across something other than a T_STRING token. Ignore. return; } + + $tstring_token = $this->phpcsFile->findNext( T_STRING, $param['start'], $param['end'] + 1 ); + + $message = 'Constant name, as a string, should be used along with `%s()`.'; + $data = [ $this->tokens[ $stackPtr ]['content'] ]; + $this->phpcsFile->addError( $message, $tstring_token, 'NotCheckingConstantName', $data ); } } diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc index b07e5e7f..ec4ab2f4 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc @@ -5,11 +5,29 @@ if ( ! defined( 'WPCOM_VIP' ) ) { // Okay. } if ( ! defined( WPCOM_VIP ) ) { // Error. - define( WPCOM_VIP ); // Error. + define( WPCOM_VIP, true ); // Error. } namespace Foo\Bar; const REST_ALLOWED_META_PREFIXES = [ 'foo-', 'bar-', 'baz-' ]; if ( defined( __NAMESPACE__ . '\REST_ALLOWED_META_PREFIXES' ) && in_array( 'foo-', REST_ALLOWED_META_PREFIXES, true ) ) { // Ok. - define( __NAMESPACE__ . REST_ALLOWED_META_PREFIXES ); // Error. + define( __NAMESPACE__ . '\\' . REST_ALLOWED_META_PREFIXES[1], $value ); // OK. } + +define( __NAMESPACE__ . '\PLUGIN_URL', \plugins_url( '/', __FILE__ ) ); // OK. +if ( defined( __NAMESPACE__ . '\\LOADED' ) ) {} // OK. + +if ( defined( $obj->constant_name_property ) === false ) { // OK. + define( $variable_containing_constant_name, $constant_value ); // OK. +} + +if ( defined( MY_PREFIX . '_CONSTANT_NAME' ) === false ) { // OK. + define( 'PREFIX_' . $variable_part, $constant_value ); // OK. +} + +if ( ! defined($generator->get()) { // OK. + define( $generator->getLast(), 'value'); // OK. +} + +$defined = defined(); // OK, ignore. +$defined = defined( /*comment*/ ); // OK, ignore. diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php index b03d06d6..9b6b4225 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.php @@ -25,9 +25,8 @@ class ConstantStringUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return [ - 7 => 1, - 8 => 1, - 14 => 1, + 7 => 1, + 8 => 1, ]; } From 8f1aa77d9a0131811e65ecd1770eb26dcab6dd76 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 12:55:48 +0200 Subject: [PATCH 029/129] LowExpiryCacheTime: improve error message The original error message read: > Low cache expiry time of "%s", it is recommended to have 300 seconds or more. ... with the `%s` being replaced with the contents of the parameter, like: > Low cache expiry time of "2 * MINUTE_IN_SECONDS", it is recommended to have 300 seconds or more. While this will be clear cut enough when a hard-coded integer is passed, there is a mental overhead as soon as a calculation is involved. This adjusts the error message to be more descriptive: * Displaying the calculated time and annotating this is in seconds. * Only displaying the content of the parameter passed when it is not a hard-coded integer. * Improving the grammar of the message. So, the above example message will now become: > Low cache expiry time of 120 seconds detected. It is recommended to have 300 seconds or more. Found: "2 * MINUTE_IN_SECONDS" --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 34ea78e6..84a7e2fb 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -81,8 +81,14 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p } if ( $time < 300 ) { - $message = 'Low cache expiry time of "%s", it is recommended to have 300 seconds or more.'; - $data = [ $parameters[4]['raw'] ]; + $message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.'; + $data = [ $time ]; + + if ( $time !== trim( $parameters[4]['raw'] ) ) { + $message .= ' Found: "%s"'; + $data[] = $parameters[4]['raw']; + } + $this->phpcsFile->addWarning( $message, $stackPtr, 'LowCacheTime', $data ); } } From d7f92aacd4e8306f7cbc4dad650051662ffc864e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 18:41:20 +0200 Subject: [PATCH 030/129] LowExpiryCacheTime: improve error line precision The error would originally be reported on the line containing the function call to the `wp_cache_*()` function. This changes that to report on the line containing the actual parameter, which for multi-line function calls may not be on the same line as where the function call keyword is found. Includes unit test. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 8 ++++++-- .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 8 ++++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 84a7e2fb..51f7c109 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -7,6 +7,7 @@ namespace WordPressVIPMinimum\Sniffs\Performance; +use PHP_CodeSniffer\Util\Tokens; use WordPressCS\WordPress\AbstractFunctionParameterSniff; /** @@ -69,7 +70,8 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p return; } - $time = $parameters[4]['raw']; + $param = $parameters[4]; + $time = $param['raw']; if ( is_numeric( $time ) === false ) { // If using time constants, we need to convert to a number. @@ -89,7 +91,9 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $data[] = $parameters[4]['raw']; } - $this->phpcsFile->addWarning( $message, $stackPtr, 'LowCacheTime', $data ); + $reportPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param['start'], $param['end'], true ); + + $this->phpcsFile->addWarning( $message, $reportPtr, 'LowCacheTime', $data ); } } } diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index eb5a63cd..2f40479d 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -38,3 +38,11 @@ wp_cache_replace( 'test', $data, $group, 100 ); // Lower than 300. wp_cache_replace( 'test', $data, $group, 2*MINUTE_IN_SECONDS ); // Lower than 300. wp_cache_replace( 123, $data, null, 1.5 * MINUTE_IN_SECONDS ); // Lower than 300. wp_cache_replace( $testing, $data, '', 1.5 * MINUTE_IN_SECONDS ); // Lower than 300. + +// Test error being reported on the line containing the parameter. +wp_cache_replace( + $testing, + $data, + '', + 1.5 * MINUTE_IN_SECONDS // Lower than 300. +); diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 81a4da60..7ee3c02e 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -46,6 +46,7 @@ public function getWarningList() { 38 => 1, 39 => 1, 40 => 1, + 47 => 1, ]; } } From 74d8b43383be1e7a0de8bd36a62c60e67b1164e3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 13:35:21 +0200 Subject: [PATCH 031/129] LowExpiryCacheTime: bug fix - calculations with simple floats Until now, calculations using a floating point number would not be handled correctly as floating point numbers were not recognized in the regex and therefore not evaluated. This means that the `( $time < 300 )` comparison would use the raw parameter text value with the replaced time constants for the comparison. To illustrate: * `1.5 * MINUTE_IN_SECONDS` is not numeric, so the condition would be entered. * In the condition the `MINUTE_IN_SECONDS` would be replaced with the actual numeric value, becoming `'1.5 * 60'`, * ... which, due to the `.` in the float `1.5`, would not match the regex and therefore would not be `eval`uated, * Ultimately resulting in a comparison of `if ( '1.5 * 60' < 300 )` - take note of the `1.5 * 60` still being a string! -, * ... which, due to the string to integer type juggle rules, would effectively become `if ( 1.5 < 300 )` - take not of the string now having been changed to a float -... PHP type juggle logic which was applied: > If the string starts with valid numeric data, this will be the value used. Otherwise, the value will be 0 (zero). Valid numeric data is an optional sign, followed by one or more digits (optionally containing a decimal point), followed by an optional exponent. The exponent is an 'e' or 'E' followed by one or more digits. Source: https://www.php.net/manual/en/language.types.string.php This would ultimately result in both false positives as well as false negatives, as illustrated by the added unit tests which would previously both fail, the first with a false positive, the second with a false negative. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 2 +- .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 4 ++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 51f7c109..4f6c61dc 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -77,7 +77,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p // If using time constants, we need to convert to a number. $time = str_replace( array_keys( $this->wp_time_constants ), $this->wp_time_constants, $time ); - if ( preg_match( '#^[\s\d+*/-]+$#', $time ) > 0 ) { + if ( preg_match( '#^[\s\d+\.*/-]+$#', $time ) > 0 ) { $time = eval( "return $time;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. } } diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 2f40479d..b26cf1f6 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -46,3 +46,7 @@ wp_cache_replace( '', 1.5 * MINUTE_IN_SECONDS // Lower than 300. ); + +// Test calculations with floats. +wp_cache_replace( $testing, $data, '', 7.5 * MINUTE_IN_SECONDS ); // OK. +wp_cache_replace( $testing, $data, '', 500 * 0.1 ); // Bad. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 7ee3c02e..fff01238 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -47,6 +47,7 @@ public function getWarningList() { 39 => 1, 40 => 1, 47 => 1, + 52 => 1, ]; } } From 1e0e6b32fc9f5a3c3b5aedded767d2997f66830a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 16:43:47 +0200 Subject: [PATCH 032/129] LowExpiryCacheTime: bug fix - allow for comments As the sniff examined the `raw` content of the parameter instead of walking the tokens, the sniff would get confused over comments found within the parameter. As the `raw` content would not be seen as _numeric_ when there is a comment, these parameter values would be subject to the same _string to integer_ type juggle rules as we saw with the handling of floats, leading to both false negatives as well as false positives. This is now fixed by changing the sniff to no longer use the `raw` parameter content, but to use token walking instead and build up the string to be `eval`uated based on the tokens encountered. The token walking is 100% based on the previous implementation - i.e. it allows for the same characters as were previously allowed via the regex -, with the only change in behaviour being the handling of comments. (well... it also adds an allowance for floats written with exponentials, but that would be an edge case no matter what) Includes unit tests. Aside from the very first new test (`/* Deliberately left empty */`), each of these would give the opposite result prior to this fix. Includes making the error message text cleaner for compound parameters, by displaying just the text string used to calculate the time, excluding comments. --- .../Performance/LowExpiryCacheTimeSniff.php | 48 +++++++++++++++---- .../LowExpiryCacheTimeUnitTest.inc | 19 ++++++++ .../LowExpiryCacheTimeUnitTest.php | 1 + 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 4f6c61dc..abbc4d57 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -70,25 +70,55 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p return; } - $param = $parameters[4]; - $time = $param['raw']; + $param = $parameters[4]; + $tokensAsString = ''; - if ( is_numeric( $time ) === false ) { - // If using time constants, we need to convert to a number. - $time = str_replace( array_keys( $this->wp_time_constants ), $this->wp_time_constants, $time ); + for ( $i = $param['start']; $i <= $param['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + $tokensAsString .= ' '; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_LNUMBER + || $this->tokens[ $i ]['code'] === T_DNUMBER + ) { + // Integer or float. + $tokensAsString .= $this->tokens[ $i ]['content']; + continue; + } - if ( preg_match( '#^[\s\d+\.*/-]+$#', $time ) > 0 ) { - $time = eval( "return $time;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. + if ( $this->tokens[ $i ]['code'] === T_MULTIPLY + || $this->tokens[ $i ]['code'] === T_DIVIDE + || $this->tokens[ $i ]['code'] === T_MINUS + ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + continue; + } + + // If using time constants, we need to convert to a number. + if ( $this->tokens[ $i ]['code'] === T_STRING + && isset( $this->wp_time_constants[ $this->tokens[ $i ]['content'] ] ) === true + ) { + $tokensAsString .= $this->wp_time_constants[ $this->tokens[ $i ]['content'] ]; + continue; } } + if ( $tokensAsString === '' ) { + // Nothing found to evaluate. + return; + } + + $tokensAsString = trim( $tokensAsString ); + $time = eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. + if ( $time < 300 ) { $message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.'; $data = [ $time ]; - if ( $time !== trim( $parameters[4]['raw'] ) ) { + if ( (string) $time !== $tokensAsString ) { $message .= ' Found: "%s"'; - $data[] = $parameters[4]['raw']; + $data[] = $tokensAsString; } $reportPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param['start'], $param['end'], true ); diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index b26cf1f6..2fa359ab 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -50,3 +50,22 @@ wp_cache_replace( // Test calculations with floats. wp_cache_replace( $testing, $data, '', 7.5 * MINUTE_IN_SECONDS ); // OK. wp_cache_replace( $testing, $data, '', 500 * 0.1 ); // Bad. + +// Test comment handling. +wp_cache_add( 'test', $data, $group, /* Deliberately left empty */ ); // OK. +wp_cache_add( 'test', $data, $group, 600 * 0.1 /* = 1 minute */ ); // Bad. +wp_cache_add( + 'test', + $data, + $group, + // Cache for 10 minutes. + 600 +); // OK. + +wp_cache_add( + 'test', + $data, + $group, + // phpcs:ignore Stnd.Cat.Sniff -- Just here for testing purposes. + 600 +); // OK. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index fff01238..1544f76a 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -48,6 +48,7 @@ public function getWarningList() { 40 => 1, 47 => 1, 52 => 1, + 56 => 1, ]; } } From c67a36a2e13efcb02be5926ab22dd8048a3b02d5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 18:58:00 +0200 Subject: [PATCH 033/129] LowExpiryCacheTime: minor efficiency tweak As the error determination has now switched to token walking, we may as well set the `$reportPtr` while walking the tokens, instead of using the `findNext()` method. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index abbc4d57..e1a5fd59 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -72,6 +72,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $param = $parameters[4]; $tokensAsString = ''; + $reportPtr = null; for ( $i = $param['start']; $i <= $param['end']; $i++ ) { if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { @@ -79,6 +80,11 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( isset( $reportPtr ) === false ) { + // Set the report pointer to the first non-empty token we encounter. + $reportPtr = $i; + } + if ( $this->tokens[ $i ]['code'] === T_LNUMBER || $this->tokens[ $i ]['code'] === T_DNUMBER ) { @@ -121,8 +127,6 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $data[] = $tokensAsString; } - $reportPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param['start'], $param['end'], true ); - $this->phpcsFile->addWarning( $message, $reportPtr, 'LowCacheTime', $data ); } } From 54f6db0fd9de5f2500d56a4fd2d429f90839dedf Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 18:35:19 +0200 Subject: [PATCH 034/129] LowExpiryCacheTime: add new warning / manual inspection needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new warning to manual inspect that the cache time is more than 300 seconds when an unexpected token (variable/non-WP-time constant) is encountered. This also prevents the sniff from throwing parse errors for the `eval`-ed code when unexpected tokens are encountered, as the `$tokensAsString` text string could easily contain parse errors now it is based on token walking instead of the regex-based check. In other words: we either need to account for all possible tokens, òr, like is done now, bow out with (or without) a warning when unexpected tokens are encountered. Fixes 572 Includes unit tests. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 7 +++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 8 ++++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index e1a5fd59..7d728b89 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -108,6 +108,13 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $tokensAsString .= $this->wp_time_constants[ $this->tokens[ $i ]['content'] ]; continue; } + + // Encountered an unexpected token. Manual inspection needed. + $message = 'Cache expiry time could not be determined. Please inspect that the fourth parameter passed to %s() evaluates to 300 seconds or more. Found: "%s"'; + $data = [ $matched_content, $parameters[4]['raw'] ]; + $this->phpcsFile->addWarning( $message, $reportPtr, 'CacheTimeUndetermined', $data ); + + return; } if ( $tokensAsString === '' ) { diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 2fa359ab..3beff4d5 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -69,3 +69,11 @@ wp_cache_add( // phpcs:ignore Stnd.Cat.Sniff -- Just here for testing purposes. 600 ); // OK. + +// Test variable/constant with or without calculation being passed. +wp_cache_set( $key, $data, '', $time ); // Manual inspection warning. +wp_cache_set( $key, $data, '', PREFIX_FIVE_MINUTES ); // Manual inspection warning. +wp_cache_set( $key, $data, '', 20 * $time ); // Manual inspection warning. +wp_cache_set( $key, $data, '', $base + $extra ); // Manual inspection warning. +wp_cache_set( $key, $data, '', 300 + $extra ); // Manual inspection warning. +wp_cache_set( $key, $data, '', PREFIX_CUSTOM_TIME * 5); // Manual inspection warning. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 1544f76a..5f0d67ca 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -49,6 +49,12 @@ public function getWarningList() { 47 => 1, 52 => 1, 56 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, ]; } } From dae7c028e5087508a4fc1d42ff4de06ab170419d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 19:14:16 +0200 Subject: [PATCH 035/129] LowExpiryCacheTime: bug fix - allow for all arithmetic operators While the `*`, `/` and `-` operators may be the ones most commonly used in the calculations for cache time, PHP contains more arithmetic operators, so let's properly account for them all. Not doing so would (and did) result in false positives/negatives previously. Includes unit tests. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 5 +---- .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 7d728b89..bbc4ebdd 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -93,10 +93,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } - if ( $this->tokens[ $i ]['code'] === T_MULTIPLY - || $this->tokens[ $i ]['code'] === T_DIVIDE - || $this->tokens[ $i ]['code'] === T_MINUS - ) { + if ( isset( Tokens::$arithmeticTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { $tokensAsString .= $this->tokens[ $i ]['content']; continue; } diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 3beff4d5..3b865b9a 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -77,3 +77,7 @@ wp_cache_set( $key, $data, '', 20 * $time ); // Manual inspection warning. wp_cache_set( $key, $data, '', $base + $extra ); // Manual inspection warning. wp_cache_set( $key, $data, '', 300 + $extra ); // Manual inspection warning. wp_cache_set( $key, $data, '', PREFIX_CUSTOM_TIME * 5); // Manual inspection warning. + +// Test calculations with additional aritmetic operators. +wp_cache_replace( 'test', $data, $group, +5 ** MINUTE_IN_SECONDS ); // OK. +wp_cache_add( 'test', $data, $group, WEEK_IN_SECONDS / 3 + HOUR_IN_SECONDS ); // OK. From 67d54cdabef67323bab309afb26ed7769e83587d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 19:25:58 +0200 Subject: [PATCH 036/129] LowExpiryCacheTime: bug fix - allow for parentheses grouping calculations Depending on code style rules, it is quite common for calculations to be grouped within parentheses. This adjusts the code to allow for this and prevents false positive "manual inspection" warnings for those type of grouped calculations. The grouped calculation will now be properly `eval`uated and only throw a warning if the resulting cache time is lower than 300. Includes unit tests. --- .../Performance/LowExpiryCacheTimeSniff.php | 28 ++++++++++++++++++- .../LowExpiryCacheTimeUnitTest.inc | 6 ++++ .../LowExpiryCacheTimeUnitTest.php | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index bbc4ebdd..76d6db5d 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -73,6 +73,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $param = $parameters[4]; $tokensAsString = ''; $reportPtr = null; + $openParens = 0; for ( $i = $param['start']; $i <= $param['end']; $i++ ) { if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { @@ -106,6 +107,18 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( $this->tokens[ $i ]['code'] === T_OPEN_PARENTHESIS ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + ++$openParens; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_CLOSE_PARENTHESIS ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + --$openParens; + continue; + } + // Encountered an unexpected token. Manual inspection needed. $message = 'Cache expiry time could not be determined. Please inspect that the fourth parameter passed to %s() evaluates to 300 seconds or more. Found: "%s"'; $data = [ $matched_content, $parameters[4]['raw'] ]; @@ -120,7 +133,20 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p } $tokensAsString = trim( $tokensAsString ); - $time = eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. + + if ( $openParens !== 0 ) { + /* + * Shouldn't be possible as that would indicate a parse error in the original code, + * but let's prevent getting parse errors in the `eval`-ed code. + */ + if ( $openParens > 0 ) { + $tokensAsString .= str_repeat( ')', $openParens ); + } else { + $tokensAsString = str_repeat( '(', abs( $openParens ) ) . $tokensAsString; + } + } + + $time = eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. if ( $time < 300 ) { $message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.'; diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 3b865b9a..60e03ee9 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -81,3 +81,9 @@ wp_cache_set( $key, $data, '', PREFIX_CUSTOM_TIME * 5); // Manual inspection war // Test calculations with additional aritmetic operators. wp_cache_replace( 'test', $data, $group, +5 ** MINUTE_IN_SECONDS ); // OK. wp_cache_add( 'test', $data, $group, WEEK_IN_SECONDS / 3 + HOUR_IN_SECONDS ); // OK. + +// Test calculations grouped with parentheses. +wp_cache_set( $key, $data, '', (24 * 60 * 60) ); // OK. +wp_cache_set( $key, $data, '', (-(2 * 60) + 600) ); // OK. +wp_cache_set( $key, $data, '', (2 * 60) ); // Bad. +wp_cache_set( $key, $data, '', (-(2 * 60) + 600 ); // OK - includes parse error, close parenthesis missing. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 5f0d67ca..1ef15de5 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -55,6 +55,7 @@ public function getWarningList() { 77 => 1, 78 => 1, 79 => 1, + 88 => 1, ]; } } From 5f32d2f25f042c0c22d00e3e977f91eeb78b17ba Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 8 Oct 2020 19:46:43 +0200 Subject: [PATCH 037/129] LowExpiryCacheTime: bug fix - allow for cache time being passed as a string There is no type declaration in effect for the `$expire` parameter of the `wp_cache_*()` functions and in each of the functions, the parameter is type cast to integer straight away. In the WP native implementation, this is done within the `wp_cache_set()`, wp_cache_add()` and `wp_cache_replace()` functions before these pass off to the `$wp_object_cache->set/add/replace()` methods. See: * https://developer.wordpress.org/reference/functions/wp_cache_set/#source * https://developer.wordpress.org/reference/functions/wp_cache_add/#source * https://developer.wordpress.org/reference/functions/wp_cache_replace/#source In the VIP object cache implementation this is done via a call to `intval()` from within the `$wp_object_cache->set/add/replace()` methods before the `$expire` parameter is used. See: * `add()`: https://github.com/Automattic/wp-memcached/blob/811243804a892a4609a4581d9479fa80c4e4ac8d/object-cache.php#L164 * `replace()`: https://github.com/Automattic/wp-memcached/blob/811243804a892a4609a4581d9479fa80c4e4ac8d/object-cache.php#L474 * `set()`: https://github.com/Automattic/wp-memcached/blob/811243804a892a4609a4581d9479fa80c4e4ac8d/object-cache.php#L522 In other words, in reality, any input which can be type cast to integer is accepted by the function. Aside from that, PHP will type cast to integer internally anyway before the parameter even reaches the function, if a calculation is done/an arithmetic operator is encountered. As things were, a "manual inspection" warning would be thrown for any parameter containing a text string, while for numeric text strings, this is not needed as we can actually calculate the cache time correctly. This adjusts the code to allow for numeric text strings being passed to the function, either on their own or as part of a calculation. While using `is_numeric()` here is not 100% correct, I deem this case a rare one to begin with, so further refinement can be done if bugs would be reported and/or when PHPCSUtils is implemented. Includes unit tests. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 8 ++++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 6 ++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 2 ++ 3 files changed, 16 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 76d6db5d..ce18b459 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -119,6 +119,14 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( $this->tokens[ $i ]['code'] === T_CONSTANT_ENCAPSED_STRING ) { + $content = $this->strip_quotes( $this->tokens[ $i ]['content'] ); + if ( is_numeric( $content ) === true ) { + $tokensAsString .= $content; + continue; + } + } + // Encountered an unexpected token. Manual inspection needed. $message = 'Cache expiry time could not be determined. Please inspect that the fourth parameter passed to %s() evaluates to 300 seconds or more. Found: "%s"'; $data = [ $matched_content, $parameters[4]['raw'] ]; diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 60e03ee9..f55db893 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -87,3 +87,9 @@ wp_cache_set( $key, $data, '', (24 * 60 * 60) ); // OK. wp_cache_set( $key, $data, '', (-(2 * 60) + 600) ); // OK. wp_cache_set( $key, $data, '', (2 * 60) ); // Bad. wp_cache_set( $key, $data, '', (-(2 * 60) + 600 ); // OK - includes parse error, close parenthesis missing. + +// Test handling of numbers passed as strings. +wp_cache_set( 'test', $data, $group, '300' ); // OK - type cast to integer within the function. +wp_cache_set( 'test', $data, $group, '100' * 3 ); // OK - type cast to integer by PHP during the calculation. +wp_cache_set( 'test', $data, $group, '-10' ); // Bad - type cast to integer within the function. +wp_cache_replace( $testing, $data, '', '1.5' * MINUTE_IN_SECONDS ); // Bad - type cast to integer by PHP during the calculation. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 1ef15de5..78e8c8d2 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -56,6 +56,8 @@ public function getWarningList() { 78 => 1, 79 => 1, 88 => 1, + 94 => 1, + 95 => 1, ]; } } From 244df86d0aeb5477d4d3faf346f63c24ea2c3bfe Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 00:29:33 +0200 Subject: [PATCH 038/129] LowExpiryCacheTime: bug fix - allow for a cache time of 0 The default value of the `$expire` parameter in both the WP native, as well as the VIP object cache implementation, is `0` which translates to "no expiration", or in the VIP object cache to the "default expiration". In other words, even though `0` is lower than `300`, it should not be flagged and should be accepted as a valid value for the cache time. As, as explained in the previous commits, the value of the parameter is cast to integer, non-integer ways of passing the parameter which result in `0`, like passing `false` or `null`, should also be taken into account. Includes unit tests. Each of which would previously have resulted in one of the two warnings. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 9 ++++++++- .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index ce18b459..6910fbcc 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -94,6 +94,13 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( $this->tokens[ $i ]['code'] === T_FALSE + || $this->tokens[ $i ]['code'] === T_NULL + ) { + $tokensAsString .= 0; + continue; + } + if ( isset( Tokens::$arithmeticTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { $tokensAsString .= $this->tokens[ $i ]['content']; continue; @@ -156,7 +163,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $time = eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. - if ( $time < 300 ) { + if ( $time < 300 && (int) $time !== 0 ) { $message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.'; $data = [ $time ]; diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index f55db893..00d7f338 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -93,3 +93,10 @@ wp_cache_set( 'test', $data, $group, '300' ); // OK - type cast to integer withi wp_cache_set( 'test', $data, $group, '100' * 3 ); // OK - type cast to integer by PHP during the calculation. wp_cache_set( 'test', $data, $group, '-10' ); // Bad - type cast to integer within the function. wp_cache_replace( $testing, $data, '', '1.5' * MINUTE_IN_SECONDS ); // Bad - type cast to integer by PHP during the calculation. + +// Test handling of 0 values. `0` is the default value for the parameter and translates internally to "no expiration". +wp_cache_add( 'test', $data, $group, 0 ); // OK. +wp_cache_add( 'test', $data, $group, 0.0 ); // OK. +wp_cache_add( 'test', $data, $group, '0' ); // OK. +wp_cache_add( 'test', $data, $group, false ); // OK. +wp_cache_add( 'test', $data, $group, null ); // OK. From 497f6557f83376eff37aef4bc982119a74e72426 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 00:34:30 +0200 Subject: [PATCH 039/129] LowExpiryCacheTime: feature completeness - allow for `true` As `false`, `null`, integers, floats and plain text strings are now all handled correctly by the sniff, it makes sense to also handle `true` for feature completeness. Along the same lines as detailed in previous commits, `true` would be cast to integer and would become `1`, so let's handle it as such. Includes unit test. This test would previously result in a "undetermined" error, while it will now, correctly, report on the cache time being too low. --- .../Performance/LowExpiryCacheTimeSniff.php | 5 ++ .../LowExpiryCacheTimeUnitTest.inc | 3 ++ .../LowExpiryCacheTimeUnitTest.php | 49 ++++++++++--------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 6910fbcc..95ad8aff 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -101,6 +101,11 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( $this->tokens[ $i ]['code'] === T_TRUE ) { + $tokensAsString .= 1; + continue; + } + if ( isset( Tokens::$arithmeticTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { $tokensAsString .= $this->tokens[ $i ]['content']; continue; diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 00d7f338..cee00932 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -100,3 +100,6 @@ wp_cache_add( 'test', $data, $group, 0.0 ); // OK. wp_cache_add( 'test', $data, $group, '0' ); // OK. wp_cache_add( 'test', $data, $group, false ); // OK. wp_cache_add( 'test', $data, $group, null ); // OK. + +// Test handling of other scalar values. +wp_cache_add( 'test', $data, $group, true ); // Bad - becomes integer 1. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 78e8c8d2..3331e7da 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -34,30 +34,31 @@ public function getErrorList() { */ public function getWarningList() { return [ - 27 => 1, - 28 => 1, - 29 => 1, - 30 => 1, - 32 => 1, - 33 => 1, - 34 => 1, - 35 => 1, - 37 => 1, - 38 => 1, - 39 => 1, - 40 => 1, - 47 => 1, - 52 => 1, - 56 => 1, - 74 => 1, - 75 => 1, - 76 => 1, - 77 => 1, - 78 => 1, - 79 => 1, - 88 => 1, - 94 => 1, - 95 => 1, + 27 => 1, + 28 => 1, + 29 => 1, + 30 => 1, + 32 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 47 => 1, + 52 => 1, + 56 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, + 88 => 1, + 94 => 1, + 95 => 1, + 105 => 1, ]; } } From 0340d86df6321a2fece7b55ee239df9cdb7e1af0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 00:58:50 +0200 Subject: [PATCH 040/129] LowExpiryCacheTime: bug fix - allow for WP time constants being passed as FQN In a namespaced file, it is common to use Fully Qualified Names for constructs in the global namespace. Alternatively, these are imported via an import `use` statement. This handles the first case for when a WP time constant is passed as an FQN. The second case should be handled at a later time once PHPCSUtils is being implemented. This commit also adds a number of additional unit tests to safeguard that something which may _look_ like a WP time constant, but isn't, correctly triggers the "Undetermined" warning. --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 9 +++++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 11 +++++++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 95ad8aff..0b9d499c 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -81,6 +81,15 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p continue; } + if ( $this->tokens[ $i ]['code'] === T_NS_SEPARATOR ) { + /* + * Ignore namespace separators. If it's part of a global WP time constant, it will be + * handled correctly. If it's used in any other context, another token *will* trigger the + * "undetermined" warning anyway. + */ + continue; + } + if ( isset( $reportPtr ) === false ) { // Set the report pointer to the first non-empty token we encounter. $reportPtr = $i; diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index cee00932..1ffa68f3 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -103,3 +103,14 @@ wp_cache_add( 'test', $data, $group, null ); // OK. // Test handling of other scalar values. wp_cache_add( 'test', $data, $group, true ); // Bad - becomes integer 1. + +// Test passing just and only one of the time constants, including passing it as an FQN. +wp_cache_set( 'test', $data, $group, HOUR_IN_SECONDS ); // OK. +wp_cache_set( 'test', $data, $group, \MONTH_IN_SECONDS ); // OK. + +// Test passing something which may look like one of the time constants, but isn't. +wp_cache_set( 'test', $data, $group, month_in_seconds ); // Bad - constants are case-sensitive. +wp_cache_set( 'test', $data, $group, HOUR_IN_SECONDS::methodName() ); // Bad - not a constant. +wp_cache_set( 'test', $data, $group, $obj->MONTH_IN_SECONDS ); // Bad - not a constant. +wp_cache_set( 'test', $data, $group, $obj::MONTH_IN_SECONDS ); // Bad - not the WP constant. +wp_cache_set( 'test', $data, $group, PluginNamespace\SubLevel\DAY_IN_SECONDS ); // Bad - not the WP constant. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 3331e7da..684390ab 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -59,6 +59,11 @@ public function getWarningList() { 94 => 1, 95 => 1, 105 => 1, + 112 => 1, + 113 => 1, + 114 => 1, + 115 => 1, + 116 => 1, ]; } } From 82ce04a6a115bfcecf380c8c0815a7de58b37340 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 01:01:50 +0200 Subject: [PATCH 041/129] LowExpiryCacheTime: add a few more unit tests ... safeguarding that certain situations are handled correctly and documenting the sniff behaviour in those cases. --- .../Tests/Performance/LowExpiryCacheTimeUnitTest.inc | 7 +++++++ .../Tests/Performance/LowExpiryCacheTimeUnitTest.php | 3 +++ 2 files changed, 10 insertions(+) diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index 1ffa68f3..a6572d6d 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -114,3 +114,10 @@ wp_cache_set( 'test', $data, $group, HOUR_IN_SECONDS::methodName() ); // Bad - n wp_cache_set( 'test', $data, $group, $obj->MONTH_IN_SECONDS ); // Bad - not a constant. wp_cache_set( 'test', $data, $group, $obj::MONTH_IN_SECONDS ); // Bad - not the WP constant. wp_cache_set( 'test', $data, $group, PluginNamespace\SubLevel\DAY_IN_SECONDS ); // Bad - not the WP constant. + +// Test passing negative number as cache time. +wp_cache_set( 'test', $data, $group, -300 ); // Bad. +wp_cache_add( $testing, $data, 'test_group', -6 * MINUTE_IN_SECONDS ); // Bad. + +// Test more complex logic in the parameter. +wp_cache_add( $key, $data, '', ($toggle ? 200 : 400) ); // Manual inspection warning. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 684390ab..9c6e98f8 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -64,6 +64,9 @@ public function getWarningList() { 114 => 1, 115 => 1, 116 => 1, + 119 => 1, + 120 => 1, + 123 => 1, ]; } } From 32aed75f7b1538d1fdb6abcca0e312b8fdd1441a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 01:31:54 +0200 Subject: [PATCH 042/129] LowExpiryCacheTime: add note about the object cache implementation used --- .../Sniffs/Performance/LowExpiryCacheTimeSniff.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 0b9d499c..7137c698 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -13,6 +13,8 @@ /** * This sniff throws a warning when low cache times are set. * + * {@internal VIP uses the Memcached object cache implementation. {@link https://github.com/Automattic/wp-memcached}} + * * @package VIPCS\WordPressVIPMinimum * * @since 0.4.0 From af82f36bff8986caec2f2defd9acb21c94ff0ba6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 19:23:55 +0200 Subject: [PATCH 043/129] Functions/DynamicCalls: annotate the unit tests ... to show which ones should fail and which should pass. --- WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index 7cde79e4..adf80eaf 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -6,10 +6,10 @@ function my_test() { $my_notokay_func = 'extract'; -$my_notokay_func(); +$my_notokay_func(); // Bad. $my_okay_func = 'my_test'; -$my_okay_func(); +$my_okay_func(); // OK. From d8f958d4f6718eeddc6d5ddbabbf9cef6f088364 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 18:47:46 +0200 Subject: [PATCH 044/129] Functions/DynamicCalls: remove redundant conditions [1] This sniff only listens to `T_VARIABLE` tokens, so checking that what was received is a `T_VARIABLE` is redundant. --- .../Sniffs/Functions/DynamicCallsSniff.php | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 660bd0e1..90721673 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -94,17 +94,6 @@ public function process_token( $stackPtr ) { * @return void */ private function collect_variables() { - /* - * Make sure we are working with a variable, - * get its value if so. - */ - - if ( - $this->tokens[ $this->stackPtr ]['type'] !== - 'T_VARIABLE' - ) { - return; - } $current_var_name = $this->tokens[ $this->stackPtr ]['content']; @@ -181,17 +170,6 @@ private function find_dynamic_calls() { return; } - /* - * Make sure we do have a variable to work with. - */ - - if ( - $this->tokens[ $this->stackPtr ]['type'] !== - 'T_VARIABLE' - ) { - return; - } - /* * If variable is not found in our registry of * variables, do nothing, as we cannot be From 0f55b11a01e5aff98496848e4d8eaf412eee34af Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 18:49:11 +0200 Subject: [PATCH 045/129] Functions/DynamicCalls: remove redundant conditions [2] The condition above checks if a token is `T_EQUAL` and bows out if it is not. The only code matching on `T_EQUAL` is the `=` operator, which will always have a `length` of `1`. --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 90721673..b8530878 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -120,10 +120,6 @@ private function collect_variables() { return; } - if ( $this->tokens[ $t_item_key ]['length'] !== 1 ) { - return; - } - /* * Find encapsulated string ( "" ) */ From db135726e0c85dd204a97f9990d7e54041ffca37 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 19:19:21 +0200 Subject: [PATCH 046/129] Functions/DynamicCalls: improve code readability * Cutting code lines off at 50 chars is maybe taking it a little too far, especially as it makes assignments hard to read. * Use proper tags in docblocks. * Improve (fix) documentation (and move it to the right place). --- .../Sniffs/Functions/DynamicCallsSniff.php | 83 +++++++------------ 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index b8530878..c7d38801 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -10,22 +10,20 @@ use WordPressVIPMinimum\Sniffs\Sniff; /** - * This sniff enforces that certain functions are not - * dynamically called. + * This sniff enforces that certain functions are not dynamically called. * * An example: - * - * + * ```php * $func = 'func_num_args'; * $func(); - * + * ``` * - * See here: http://php.net/manual/en/migration71.incompatible.php + * Note that this sniff does not catch all possible forms of dynamic calling, only some. * - * Note that this sniff does not catch all possible forms of dynamic - * calling, only some. + * @link http://php.net/manual/en/migration71.incompatible.php */ class DynamicCallsSniff extends Sniff { + /** * Functions that should not be called dynamically. * @@ -44,10 +42,11 @@ class DynamicCallsSniff extends Sniff { ]; /** - * Array of functions encountered, along with their values. - * Populated on run-time. + * Array of variable assignments encountered, along with their values. * - * @var array + * Populated at run-time. + * + * @var array The key is the name of the variable, the value, its assigned value. */ private $variables_arr = []; @@ -61,8 +60,6 @@ class DynamicCallsSniff extends Sniff { /** * Returns the token types that this sniff is interested in. * - * We want everything variable- and function-related. - * * @return array(int) */ public function register() { @@ -87,9 +84,8 @@ public function process_token( $stackPtr ) { } /** - * Finds any variable-definitions in the file being processed, - * and stores them internally in a private array. The data stored - * is the name of the variable and its assigned value. + * Finds any variable-definitions in the file being processed and stores them + * internally in a private array. * * @return void */ @@ -98,11 +94,9 @@ private function collect_variables() { $current_var_name = $this->tokens[ $this->stackPtr ]['content']; /* - * Find assignments ( $foo = "bar"; ) - * -- do this by finding all non-whitespaces, and - * check if the first one is T_EQUAL. + * Find assignments ( $foo = "bar"; ) by finding all non-whitespaces, + * and checking if the first one is T_EQUAL. */ - $t_item_key = $this->phpcsFile->findNext( [ T_WHITESPACE ], $this->stackPtr + 1, @@ -121,7 +115,7 @@ private function collect_variables() { } /* - * Find encapsulated string ( "" ) + * Find encapsulated string ( "" ). */ $t_item_key = $this->phpcsFile->findNext( [ T_CONSTANT_ENCAPSED_STRING ], @@ -137,74 +131,53 @@ private function collect_variables() { } /* - * We have found variable-assignment, - * register its name and value in the + * We have found variable-assignment, register its name and value in the * internal array for later usage. */ + $current_var_value = $this->tokens[ $t_item_key ]['content']; - $current_var_value = - $this->tokens[ $t_item_key ]['content']; - - $this->variables_arr[ $current_var_name ] = - str_replace( "'", '', $current_var_value ); + $this->variables_arr[ $current_var_name ] = str_replace( "'", '', $current_var_value ); } /** * Find any dynamic calls being made using variables. - * Report on this when found, using name of the function - * in the message. + * + * Report on this when found, using the name of the function in the message. * * @return void */ private function find_dynamic_calls() { - /* - * No variables detected; no basis for doing - * anything - */ - + // No variables detected; no basis for doing anything. if ( empty( $this->variables_arr ) ) { return; } /* - * If variable is not found in our registry of - * variables, do nothing, as we cannot be - * sure that the function being called is one of the - * blacklisted ones. + * If variable is not found in our registry of variables, do nothing, as we cannot be + * sure that the function being called is one of the blacklisted ones. */ - - if ( ! isset( - $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] - ) ) { + if ( ! isset( $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ) ) { return; } /* - * Check if we have an '(' next, or separated by whitespaces - * from our current position. + * Check if we have an '(' next, or separated by whitespaces from our current position. */ $i = 0; do { $i++; - } while ( - $this->tokens[ $this->stackPtr + $i ]['type'] === - 'T_WHITESPACE' - ); + } while ( $this->tokens[ $this->stackPtr + $i ]['type'] === 'T_WHITESPACE' ); - if ( - $this->tokens[ $this->stackPtr + $i ]['type'] !== - 'T_OPEN_PARENTHESIS' - ) { + if ( $this->tokens[ $this->stackPtr + $i ]['type'] !== 'T_OPEN_PARENTHESIS' ) { return; } $t_item_key = $this->stackPtr + $i; /* - * We have a variable match, but make sure it contains name - * of a function which is on our blacklist. + * We have a variable match, but make sure it contains name of a function which is on our blacklist. */ if ( ! in_array( From b6ebf8669c500241893cd384178e80696418bcad Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 19:19:53 +0200 Subject: [PATCH 047/129] Functions/DynamicCalls: minor simplification Join two conditions which both return anyway. --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index c7d38801..9738b162 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -106,11 +106,7 @@ private function collect_variables() { true ); - if ( $t_item_key === false ) { - return; - } - - if ( $this->tokens[ $t_item_key ]['type'] !== 'T_EQUAL' ) { + if ( $t_item_key === false || $this->tokens[ $t_item_key ]['code'] !== T_EQUAL ) { return; } From df8c596f1e1b6013f15bc54d65fe6eaa93ba334c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 18:52:33 +0200 Subject: [PATCH 048/129] Functions/DynamicCalls: bug fix - ignore comments [1] Skip over both whitespace, as well as comments. This reduces false negatives. Includes unit test. --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 3 ++- WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc | 4 ++-- WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 9738b162..0c6210f5 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -7,6 +7,7 @@ namespace WordPressVIPMinimum\Sniffs\Functions; +use PHP_CodeSniffer\Util\Tokens; use WordPressVIPMinimum\Sniffs\Sniff; /** @@ -98,7 +99,7 @@ private function collect_variables() { * and checking if the first one is T_EQUAL. */ $t_item_key = $this->phpcsFile->findNext( - [ T_WHITESPACE ], + Tokens::$emptyTokens, $this->stackPtr + 1, null, true, diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index adf80eaf..49f207a4 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -11,5 +11,5 @@ $my_notokay_func(); // Bad. $my_okay_func = 'my_test'; $my_okay_func(); // OK. - - +$test_with_comment /*comment*/ = 'func_get_args'; +$test_with_comment(); // Bad. diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php index 98057179..51f06e72 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php @@ -25,7 +25,8 @@ class DynamicCallsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return [ - 9 => 1, + 9 => 1, + 15 => 1, ]; } From 8e6362745f750d1743c4a2fc37eedecdb1a01aa2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 19:35:38 +0200 Subject: [PATCH 049/129] Functions/DynamicCalls: bug fix - don't blindly use the next text string A variable value may be build up of multiple tokens. As it was, the sniff would look for the first text string token after the equal sign within the variable assignment statement, but this disregards that: 1. The text string token found may not be the only token in the statement. 2. A statement can end on a PHP close tag (possibly a bug in PHPCS itself, but that's another matter), which would lead the sniff to look at the next statement for text strings. Fixed now. Includes unit tests, the first four of which resulted in false positives previously. --- .../Sniffs/Functions/DynamicCallsSniff.php | 36 ++++++++++++------- .../Tests/Functions/DynamicCallsUnitTest.inc | 17 +++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 0c6210f5..acbc6661 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -112,26 +112,36 @@ private function collect_variables() { } /* - * Find encapsulated string ( "" ). + * Find assignments which only assign a plain text string. */ - $t_item_key = $this->phpcsFile->findNext( - [ T_CONSTANT_ENCAPSED_STRING ], - $t_item_key + 1, - null, - false, - null, - true - ); + $end_of_statement = $this->phpcsFile->findNext( [ T_SEMICOLON, T_CLOSE_TAG ], ( $t_item_key + 1 ) ); + $value_ptr = null; + + for ( $i = $t_item_key + 1; $i < $end_of_statement; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + continue; + } + + if ( $this->tokens[ $i ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { + // Not a plain text string value. Value cannot be determined reliably. + return; + } + + $value_ptr = $i; + } - if ( $t_item_key === false ) { + if ( isset( $value_ptr ) === false ) { + // Parse error. Bow out. return; } /* - * We have found variable-assignment, register its name and value in the - * internal array for later usage. + * If we reached the end of the loop and the $value_ptr was set, we know for sure + * this was a plain text string variable assignment. + * + * Register its name and value in the internal array for later usage. */ - $current_var_value = $this->tokens[ $t_item_key ]['content']; + $current_var_value = $this->tokens[ $value_ptr ]['content']; $this->variables_arr[ $current_var_name ] = str_replace( "'", '', $current_var_value ); } diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index 49f207a4..cbacf107 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -13,3 +13,20 @@ $my_okay_func(); // OK. $test_with_comment /*comment*/ = 'func_get_args'; $test_with_comment(); // Bad. + +$test_getting_the_actual_value_1 = function_call( 'extract' ); +$test_getting_the_actual_value_1(); // OK. Unclear what the actual variable value will be. + +$test_getting_the_actual_value_2 = $array['compact']; +$test_getting_the_actual_value_2(); // OK. Unclear what the actual variable value will be. + +$test_getting_the_actual_value_3 = 10 ?> +
html
+ Date: Fri, 23 Oct 2020 19:52:27 +0200 Subject: [PATCH 050/129] Functions/DynamicCalls: bug fix - allow for double quotes Text strings can use both single quotes as well as double quotes. When the text string contains an interpolated variable, it will be tokenized as `T_DOUBLE_QUOTED_STRING`, but when it is a plain text string, a double quoted text string will be tokenized as `T_CONSTANT_ENCAPSED_STRING`, same as single quoted text string. The sniff did not take this into account, leading to false negatives. The sniff also would strip quotes from within a text - `'my\'text'` - . This did not cause a problem for this sniff as function names cannot have back slashes in them, but it was still wrong. Fixed now by using the WPCS `strip_quotes()` method. Includes unit test which would fail previously. --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 4 ++-- WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc | 3 +++ WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index acbc6661..715cbf5f 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -141,9 +141,9 @@ private function collect_variables() { * * Register its name and value in the internal array for later usage. */ - $current_var_value = $this->tokens[ $value_ptr ]['content']; + $current_var_value = $this->strip_quotes( $this->tokens[ $value_ptr ]['content'] ); - $this->variables_arr[ $current_var_name ] = str_replace( "'", '', $current_var_value ); + $this->variables_arr[ $current_var_name ] = $current_var_value; } /** diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index cbacf107..b8510e09 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -30,3 +30,6 @@ $test_getting_the_actual_value_4 = 'get_defined_vars' . $source; $test_getting_the_actual_value_4(); // OK. Unclear what the actual variable value will be. $ensure_no_notices_are_thrown_on_parse_error = /*comment*/ ; + +$test_double_quoted_string = "assert"; +$test_double_quoted_string(); // Bad. diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php index 51f06e72..4b8e764d 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php @@ -27,6 +27,7 @@ public function getErrorList() { return [ 9 => 1, 15 => 1, + 35 => 1, ]; } From b093d623e4d67e22939cc14f38f8b88d06922bab Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 20:21:18 +0200 Subject: [PATCH 051/129] Functions/DynamicCalls: bug fix - fix memory + performance issue The sniff maintains a cache of all the variables it has seen and their assigned value. When a variable is encountered, it would: * Check if it was an (plain text) assignment and if so, register the variable name + value to the cache. * Next, call the `find_dynamic_calls()` method, which first checks if any variables have been registered to the cache before doing anything. * And then checks for dynamic function calls and if one is found, checks if the variable used is one registered in the cache with a value we are looking for. This is highly inefficient as text string variable assignments are common and, as it was, _every single one_ would be added to the cache. With a large code base, that means that the cache could grow pretty large. It also means that the logic to determine if something is a dynamic function call would be executed even when there would be no text strings registered in the cache which could match any of the ones we're looking for. By changing the order of the logic, the memory leak and performance inefficiency is removed. With the updated logic, the sniff will: * Check if it was an (plain text) assignment **and if the text string matches one we're looking for** and if so, register the variable name + value to the cache. * Next, call the `find_dynamic_calls()` method, which first checks if any variables have been registered to the cache before doing anything. * And then checks for dynamic function calls. This means that if none of the previous assignments encountered matches any of the target text strings (~ 99% of the time), this sniff will bow out at step 2 before executing the logic to check if a variable assignment is a dynamic function call. --- .../Sniffs/Functions/DynamicCallsSniff.php | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 715cbf5f..b861e595 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -31,15 +31,15 @@ class DynamicCallsSniff extends Sniff { * @var array */ private $blacklisted_functions = [ - 'assert', - 'compact', - 'extract', - 'func_get_args', - 'func_get_arg', - 'func_num_args', - 'get_defined_vars', - 'mb_parse_str', - 'parse_str', + 'assert' => true, + 'compact' => true, + 'extract' => true, + 'func_get_args' => true, + 'func_get_arg' => true, + 'func_num_args' => true, + 'get_defined_vars' => true, + 'mb_parse_str' => true, + 'parse_str' => true, ]; /** @@ -138,11 +138,17 @@ private function collect_variables() { /* * If we reached the end of the loop and the $value_ptr was set, we know for sure * this was a plain text string variable assignment. - * - * Register its name and value in the internal array for later usage. */ $current_var_value = $this->strip_quotes( $this->tokens[ $value_ptr ]['content'] ); + if ( isset( $this->blacklisted_functions[ $current_var_value ] ) === false ) { + // Text string is not one of the ones we're looking for. + return; + } + + /* + * Register the variable name and value in the internal array for later usage. + */ $this->variables_arr[ $current_var_name ] = $current_var_value; } @@ -183,19 +189,6 @@ private function find_dynamic_calls() { $t_item_key = $this->stackPtr + $i; - /* - * We have a variable match, but make sure it contains name of a function which is on our blacklist. - */ - - if ( ! in_array( - $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ], - $this->blacklisted_functions, - true - ) ) { - return; - } - - // We do, so report. $message = 'Dynamic calling is not recommended in the case of %s.'; $data = [ $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ]; $this->phpcsFile->addError( $message, $t_item_key, 'DynamicCalls', $data ); From 6b4a43dce66c6d4faacbfe81fad632240bcfd1d4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 20:26:03 +0200 Subject: [PATCH 052/129] Functions/DynamicCalls: rename private property Rename the `private` `$blacklisted_functions` property to `$function_names` to get rid of the use of a non-inclusive term. --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index b861e595..485674bb 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -30,7 +30,7 @@ class DynamicCallsSniff extends Sniff { * * @var array */ - private $blacklisted_functions = [ + private $function_names = [ 'assert' => true, 'compact' => true, 'extract' => true, @@ -141,7 +141,7 @@ private function collect_variables() { */ $current_var_value = $this->strip_quotes( $this->tokens[ $value_ptr ]['content'] ); - if ( isset( $this->blacklisted_functions[ $current_var_value ] ) === false ) { + if ( isset( $this->function_names[ $current_var_value ] ) === false ) { // Text string is not one of the ones we're looking for. return; } From 3118358cf36a3abbff065dc527f3eea59babb773 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 20:40:20 +0200 Subject: [PATCH 053/129] Functions/DynamicCalls: bug fix - ignore comments [2] Skip over both whitespace, as well as comments and take live coding into account. This reduces false negatives, as well as fixing issue 590. Includes unit tests. Fixes 590 --- .../Sniffs/Functions/DynamicCallsSniff.php | 16 ++++------------ .../Tests/Functions/DynamicCallsUnitTest.inc | 5 ++++- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 485674bb..afb64832 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -174,23 +174,15 @@ private function find_dynamic_calls() { } /* - * Check if we have an '(' next, or separated by whitespaces from our current position. + * Check if we have an '(' next. */ - - $i = 0; - - do { - $i++; - } while ( $this->tokens[ $this->stackPtr + $i ]['type'] === 'T_WHITESPACE' ); - - if ( $this->tokens[ $this->stackPtr + $i ]['type'] !== 'T_OPEN_PARENTHESIS' ) { + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $this->stackPtr + 1 ), null, true ); + if ( $next === false || $this->tokens[ $next ]['code'] !== T_OPEN_PARENTHESIS ) { return; } - $t_item_key = $this->stackPtr + $i; - $message = 'Dynamic calling is not recommended in the case of %s.'; $data = [ $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ]; - $this->phpcsFile->addError( $message, $t_item_key, 'DynamicCalls', $data ); + $this->phpcsFile->addError( $message, $this->stackPtr, 'DynamicCalls', $data ); } } diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index b8510e09..fc307b2f 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -12,7 +12,7 @@ $my_okay_func = 'my_test'; $my_okay_func(); // OK. $test_with_comment /*comment*/ = 'func_get_args'; -$test_with_comment(); // Bad. +$test_with_comment /*comment*/ (); // Bad. $test_getting_the_actual_value_1 = function_call( 'extract' ); $test_getting_the_actual_value_1(); // OK. Unclear what the actual variable value will be. @@ -33,3 +33,6 @@ $ensure_no_notices_are_thrown_on_parse_error = /*comment*/ ; $test_double_quoted_string = "assert"; $test_double_quoted_string(); // Bad. + +// Intentional parse error. This has to be the last test in the file. +$my_notokay_func From 1d1a9777122ee1c57eb292050dbfefa48e4e7191 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Oct 2020 20:43:53 +0200 Subject: [PATCH 054/129] Functions/DynamicCalls: error message tweak --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index afb64832..36c92021 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -181,7 +181,7 @@ private function find_dynamic_calls() { return; } - $message = 'Dynamic calling is not recommended in the case of %s.'; + $message = 'Dynamic calling is not recommended in the case of %s().'; $data = [ $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ]; $this->phpcsFile->addError( $message, $this->stackPtr, 'DynamicCalls', $data ); } From 20fdf8bf4e6dc3e727124306b054e715427c7989 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 19 Oct 2020 10:26:13 +0200 Subject: [PATCH 055/129] WPQueryParams: sniff for `exclude` array key being set This: * Switches the `WordPressVIPMinimum.Performance.WPQueryParams` sniff over to use the WordPressCS `AbstractArrayAssignmentRestrictionsSniff` as the parent sniff. * Adds sniffing for the `exclude` array key via the `AbstractArrayAssignmentRestrictionsSniff` sniff logic. Includes unit test. --- .../Sniffs/Performance/WPQueryParamsSniff.php | 52 +++++++++++++++++-- .../Performance/WPQueryParamsUnitTest.inc | 6 ++- .../Performance/WPQueryParamsUnitTest.php | 1 + 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php index 949e3f5b..3a13ea44 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php @@ -8,7 +8,7 @@ namespace WordPressVIPMinimum\Sniffs\Performance; -use WordPressVIPMinimum\Sniffs\Sniff; +use WordPressCS\WordPress\AbstractArrayAssignmentRestrictionsSniff; use PHP_CodeSniffer\Util\Tokens; /** @@ -16,7 +16,7 @@ * * @package VIPCS\WordPressVIPMinimum */ -class WPQueryParamsSniff extends Sniff { +class WPQueryParamsSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Returns an array of tokens this test wants to listen for. @@ -24,8 +24,38 @@ class WPQueryParamsSniff extends Sniff { * @return array */ public function register() { + $targets = parent::register(); + + // Add the target for the "old" implementation. + $targets[] = T_CONSTANT_ENCAPSED_STRING; + + return $targets; + } + + /** + * Groups of variables to restrict. + * This should be overridden in extending classes. + * + * Example: groups => array( + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) + * ) + * + * @return array + */ + public function getGroups() { return [ - T_CONSTANT_ENCAPSED_STRING, + 'PostNotIn' => [ + 'type' => 'warning', + 'message' => 'Using `exclude`, which is subsequently used by `post__not_in`, should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information.', + 'keys' => [ + 'exclude', + ], + ], ]; } @@ -54,6 +84,22 @@ public function process_token( $stackPtr ) { $message = 'Using `post__not_in` should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'PostNotIn' ); } + + parent::process_token( $stackPtr ); + } + + /** + * Callback to process a confirmed key which doesn't need custom logic, but should always error. + * + * @param string $key Array index / key. + * @param mixed $val Assigned value. + * @param int $line Token line. + * @param array $group Group definition. + * @return mixed FALSE if no match, TRUE if matches, STRING if matches + * with custom error message passed to ->process(). + */ + public function callback( $key, $val, $line, $group ) { + return true; } } diff --git a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc index 51590746..2aecfee5 100644 --- a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc @@ -16,4 +16,8 @@ $q = new WP_Query( $query_args ); $query_args[ 'suppress_filters' ] = true; -$q = new WP_query( $query_args ); \ No newline at end of file +$q = new WP_query( $query_args ); + +get_posts( [ 'exclude' => $post_ids ] ); // Warning. + +$exclude = [ 1, 2, 3 ]; diff --git a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php index 1ccbfc4c..7d09c201 100644 --- a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php @@ -39,6 +39,7 @@ public function getWarningList() { return [ 4 => 1, 11 => 1, + 21 => 1, ]; } From 1b8c9b9327001d95e47401858e43052082f41188 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Oct 2020 15:31:08 +0200 Subject: [PATCH 056/129] Security/Underscorejs: sniff all text string tokens So far, only single quoted text strings, heredocs and inline HTML were sniffed. However, there are two more text string token types: nowdoc and double quoted strings. This adds these tokens to the `register()` method to be sniffed. This commit also ensures that there is at least one unit test in place for each of these token types and that the tests use a variety of whitespace. --- .../Sniffs/Security/UnderscorejsSniff.php | 11 +++--- .../Tests/Security/UnderscorejsUnitTest.inc | 34 ++++++++++++++++++- .../Tests/Security/UnderscorejsUnitTest.php | 8 +++++ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index fb1c0af0..0c6551b3 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -8,6 +8,7 @@ namespace WordPressVIPMinimum\Sniffs\Security; +use PHP_CodeSniffer\Util\Tokens; use WordPressVIPMinimum\Sniffs\Sniff; /** @@ -30,12 +31,10 @@ class UnderscorejsSniff extends Sniff { * @return array */ public function register() { - return [ - T_CONSTANT_ENCAPSED_STRING, - T_PROPERTY, - T_INLINE_HTML, - T_HEREDOC, - ]; + $targets = Tokens::$textStringTokens; + $targets[] = T_PROPERTY; + + return $targets; } /** diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index 2e8b7cd6..a63efb3a 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -12,6 +12,38 @@ EOT; + +" data-origsrc="<%- data.originalSrc %>">'. // NOK x 1. + '<%=data.alt%>'. // NOK x 1. + ''; +} + +function single_quoted_string_with_concatenation( $data ) { + echo '<%- data.alt %>'; // NOK x 1. +} + +function double_quoted_string( $name, $value, $is_template ) { + echo $is_template ? "<%={$name}%>" : esc_attr( $value ); // NOK. +} + +$nowdoc = <<<'EOT' + +EOT; + +$heredoc = << + <%= name %> + +EOD; diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 1043c3e8..28b6b143 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -36,6 +36,14 @@ public function getWarningList() { return [ 6 => 1, 14 => 1, + 22 => 1, + 23 => 1, + 28 => 1, + 32 => 1, + 38 => 1, + 45 => 1, + 46 => 1, + 47 => 1, ]; } From 3b18b7726303ebf5664798707cdf21e4e7d7ad01 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Oct 2020 16:21:12 +0200 Subject: [PATCH 057/129] Security/Underscorejs: throw an error for each violation So far, the sniff would throw one error per text string, independently of how often the unescaped output notation would occur in the text string. While for simple text strings, this is not much of an issue, for long and complex text strings, it may be more difficult to spot all the unescaped output notations in the text string. This changes the sniff to: * Throw a warning for each occurrence of the unescaped output notation in a text string. * Include a snippet from the text string with the details of the unescaped output notation. Old: `Found Underscore.js unescaped output notation: "<%=".` New: `Found Underscore.js unescaped output notation: "<%= post_url %>".` To allow the sniff to also match on a text string being concatenated together, the regex has to also match when `<%=` occurs at the end of the text string and will only display `<%=` in that case. To this end, we also need to remove the text string quotes (if they exist) from the token content. --- .../Sniffs/Security/UnderscorejsSniff.php | 23 +++++++++++++++---- .../Tests/Security/UnderscorejsUnitTest.php | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index 0c6551b3..05065104 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -18,6 +18,14 @@ */ class UnderscorejsSniff extends Sniff { + /** + * Regex to match unescaped output notations containing variable interpolation + * and retrieve a code snippet. + * + * @var string + */ + const UNESCAPED_INTERPOLATE_REGEX = '`<%=\s*(?:.+?%>|$)`'; + /** * A list of tokenizers this sniff supports. * @@ -46,13 +54,18 @@ public function register() { */ public function process_token( $stackPtr ) { - if ( strpos( $this->tokens[ $stackPtr ]['content'], '<%=' ) !== false ) { - // Underscore.js unescaped output. - $message = 'Found Underscore.js unescaped output notation: "<%=".'; - $this->phpcsFile->addWarning( $message, $stackPtr, 'OutputNotation' ); + $content = $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ); + $match_count = preg_match_all( self::UNESCAPED_INTERPOLATE_REGEX, $content, $matches ); + if ( $match_count > 0 ) { + foreach ( $matches[0] as $match ) { + // Underscore.js unescaped output. + $message = 'Found Underscore.js unescaped output notation: "%s".'; + $data = [ $match ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'OutputNotation', $data ); + } } - if ( strpos( $this->tokens[ $stackPtr ]['content'], 'interpolate' ) !== false ) { + if ( strpos( $content, 'interpolate' ) !== false ) { // Underscore.js unescaped output. $message = 'Found Underscore.js delimiter change notation.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'InterpolateFound' ); diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 28b6b143..4c77e2d8 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -40,7 +40,7 @@ public function getWarningList() { 23 => 1, 28 => 1, 32 => 1, - 38 => 1, + 38 => 3, 45 => 1, 46 => 1, 47 => 1, From ece1afd6d0d9ec1033370382313b060e4c0d4c4b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Oct 2020 12:56:19 +0200 Subject: [PATCH 058/129] Security/Underscorejs: add JS test case file Add a basic JS test case file. Tests for `interpolate` will be added in a follow-up commit. --- .../Tests/Security/UnderscorejsUnitTest.js | 6 +++ .../Tests/Security/UnderscorejsUnitTest.php | 40 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js new file mode 100644 index 00000000..9afbcdde --- /dev/null +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js @@ -0,0 +1,6 @@ + +var html = _.template('
  • <%- name %>
  • ', { name: 'John Smith' }); // OK. + +var html = _.template('
  • <%= name %>
  • ', { name: 'John Smith' }); // NOK. +var html = _.template('
  • <%=type.item%>
  • ', { name: 'John Smith' }); // NOK. + diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 4c77e2d8..821ed42d 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -30,21 +30,35 @@ public function getErrorList() { /** * Returns the lines where warnings should occur. * + * @param string $testFile The name of the file being tested. + * * @return array => */ - public function getWarningList() { - return [ - 6 => 1, - 14 => 1, - 22 => 1, - 23 => 1, - 28 => 1, - 32 => 1, - 38 => 3, - 45 => 1, - 46 => 1, - 47 => 1, - ]; + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'UnderscorejsUnitTest.inc': + return [ + 6 => 1, + 14 => 1, + 22 => 1, + 23 => 1, + 28 => 1, + 32 => 1, + 38 => 3, + 45 => 1, + 46 => 1, + 47 => 1, + ]; + + case 'UnderscorejsUnitTest.js': + return [ + 4 => 1, + 5 => 1, + ]; + + default: + return []; + } } } From 70ec8114d7f099547a732c5367ee49123ef6a757 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Oct 2020 17:42:57 +0200 Subject: [PATCH 059/129] Security/Underscorejs: improve check for `interpolate` in JS files The `interpolate` property in JS can be set using various syntaxes. While the current check will in no way catch them all, the changes now made should improve the accuracy of detecting a change in the delimiter via `interpolate` in JS files. Notes: * Add checking for `T_STRING` to detect `_.templateSettings.interpolate =` syntax. This was previously not detected (false negative). * Add some defensive coding to prevent false positives when the `interpolate` keyword is used in another context than underscorejs. * Limit the check for `T_STRING` and `T_PROPERTY` to Javascript files only to prevent false positives when scanning PHP files. * Limit the check for `interpolate` in text strings to PHP only. This prevents false positives when the keyword is used in a text string in JS. Includes unit tests covering all the above. --- .../Sniffs/Security/UnderscorejsSniff.php | 47 ++++++++++++++++++- .../Tests/Security/UnderscorejsUnitTest.inc | 3 ++ .../Tests/Security/UnderscorejsUnitTest.js | 20 ++++++++ .../Tests/Security/UnderscorejsUnitTest.php | 7 ++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index 05065104..8e81e602 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -41,6 +41,7 @@ class UnderscorejsSniff extends Sniff { public function register() { $targets = Tokens::$textStringTokens; $targets[] = T_PROPERTY; + $targets[] = T_STRING; return $targets; } @@ -53,6 +54,46 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { + /* + * Check for delimiter change in JS files. + */ + if ( $this->tokens[ $stackPtr ]['code'] === T_STRING + || $this->tokens[ $stackPtr ]['code'] === T_PROPERTY + ) { + if ( $this->phpcsFile->tokenizerType !== 'JS' ) { + // These tokens are only relevant for JS files. + return; + } + + if ( $this->tokens[ $stackPtr ]['content'] !== 'interpolate' ) { + return; + } + + // Check the context to prevent false positives. + if ( $this->tokens[ $stackPtr ]['code'] === T_STRING ) { + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( $prev === false || $this->tokens[ $prev ]['code'] !== T_OBJECT_OPERATOR ) { + return; + } + + $prevPrev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( ( $prevPrev === false + || $this->tokens[ $prevPrev ]['code'] !== T_STRING + || $this->tokens[ $prevPrev ]['content'] !== 'templateSettings' ) + && ( $next === false + || $this->tokens[ $next ]['code'] !== T_EQUAL ) + ) { + return; + } + } + + // Underscore.js delimiter change. + $message = 'Found Underscore.js delimiter change notation.'; + $this->phpcsFile->addWarning( $message, $stackPtr, 'InterpolateFound' ); + + return; + } $content = $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ); $match_count = preg_match_all( self::UNESCAPED_INTERPOLATE_REGEX, $content, $matches ); @@ -65,8 +106,10 @@ public function process_token( $stackPtr ) { } } - if ( strpos( $content, 'interpolate' ) !== false ) { - // Underscore.js unescaped output. + if ( $this->phpcsFile->tokenizerType !== 'JS' + && strpos( $content, 'interpolate' ) !== false + ) { + // Underscore.js delimiter change. $message = 'Found Underscore.js delimiter change notation.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'InterpolateFound' ); } diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index a63efb3a..5412770b 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -47,3 +47,6 @@ $heredoc = << EOD; + +// Make sure the JS specific check does not trigger on PHP code. +$obj->interpolate = true; diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js index 9afbcdde..b0dca205 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.js @@ -4,3 +4,23 @@ var html = _.template('
  • <%- name %>
  • ', { name: 'John Smith' }); // OK. var html = _.template('
  • <%= name %>
  • ', { name: 'John Smith' }); // NOK. var html = _.template('
  • <%=type.item%>
  • ', { name: 'John Smith' }); // NOK. +_.templateSettings.interpolate = /\{\{(.+?)\}\}/g; /* NOK */ +_.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g /* NOK */ +}; + +options.interpolate=_.templateSettings.interpolate; /* NOK */ +var interpolate = options.interpolate || reNoMatch, /* Ignore */ + source = "__p += '"; + +var template = _.template('
  • {{ name }}
  • '); /* NOK, due to the interpolate, but not flagged. */ + +// Prevent false positives on "interpolate". +var preventMisidentification = 'text interpolate text'; // OK. +var interpolate = THREE.CurveUtils.interpolate; // OK. + +var p = function(f, d) { + return s.interpolate(m(f), _(d), 0.5, e.color_space) // OK. +} + +y.interpolate.bezier = b; // OK. diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 821ed42d..8cc34042 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -52,8 +52,11 @@ public function getWarningList( $testFile = '' ) { case 'UnderscorejsUnitTest.js': return [ - 4 => 1, - 5 => 1, + 4 => 1, + 5 => 1, + 7 => 1, + 9 => 1, + 12 => 1, ]; default: From d3ce1d03c58d72a83ffeb482ba0a283c1b0262a1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 21 Oct 2020 04:00:31 +0200 Subject: [PATCH 060/129] Security/Underscorejs: improve check for `interpolate` in PHP files Match the `interpolate` property in PHP files with the similar precision as in JS files. Includes unit tests. --- .../Sniffs/Security/UnderscorejsSniff.php | 9 +++++++- .../Tests/Security/UnderscorejsUnitTest.inc | 23 +++++++++++++++++++ .../Tests/Security/UnderscorejsUnitTest.php | 2 ++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index 8e81e602..33f9f4a7 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -26,6 +26,13 @@ class UnderscorejsSniff extends Sniff { */ const UNESCAPED_INTERPOLATE_REGEX = '`<%=\s*(?:.+?%>|$)`'; + /** + * Regex to match the "interpolate" keyword when used to overrule the ERB-style delimiters. + * + * @var string + */ + const INTERPOLATE_KEYWORD_REGEX = '`(?:templateSettings\.interpolate|\.interpolate\s*=\s*/|interpolate\s*:\s*/)`'; + /** * A list of tokenizers this sniff supports. * @@ -107,7 +114,7 @@ public function process_token( $stackPtr ) { } if ( $this->phpcsFile->tokenizerType !== 'JS' - && strpos( $content, 'interpolate' ) !== false + && preg_match( self::INTERPOLATE_KEYWORD_REGEX, $content ) > 0 ) { // Underscore.js delimiter change. $message = 'Found Underscore.js delimiter change notation.'; diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index 5412770b..48ff5ea6 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -50,3 +50,26 @@ EOD; // Make sure the JS specific check does not trigger on PHP code. $obj->interpolate = true; + +// Test matching the "interpolate" keyword with higher precision (mirrors same check in JS). +function test_interpolate_match_precision() { + ?> + + 1, 46 => 1, 47 => 1, + 58 => 1, + 60 => 1, ]; case 'UnderscorejsUnitTest.js': From 1ab279496f6eb4a634163514406f5f97816a8c30 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 21 Oct 2020 04:26:10 +0200 Subject: [PATCH 061/129] Security/Underscorejs: bug fix - don't error when variable is escaped Prevent triggering a warning when the variable being printed is escaped using `_.escape()`. Includes unit tests. Fixes 345 --- .../Sniffs/Security/UnderscorejsSniff.php | 4 +++ .../Tests/Security/UnderscorejsUnitTest.inc | 33 +++++++++++++++++++ .../Tests/Security/UnderscorejsUnitTest.js | 15 +++++++++ 3 files changed, 52 insertions(+) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index 33f9f4a7..2bd7da70 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -106,6 +106,10 @@ public function process_token( $stackPtr ) { $match_count = preg_match_all( self::UNESCAPED_INTERPOLATE_REGEX, $content, $matches ); if ( $match_count > 0 ) { foreach ( $matches[0] as $match ) { + if ( strpos( $match, '_.escape(' ) !== false ) { + continue; + } + // Underscore.js unescaped output. $message = 'Found Underscore.js unescaped output notation: "%s".'; $data = [ $match ]; diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index 48ff5ea6..f3103f02 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -73,3 +73,36 @@ function test_interpolate_match_precision() { <%= _.escape(name) %>', { name: 'John Smith' }); // OK. + + var html = _.template( + "
    The \"<% __p+=_.escape(o.text) %>\" is the same
    " + // OK. + "as the \"<%= _.escape(o.text) %>\" and the same
    " + // OK. + "as the \"<%- o.text %>\"
    ", // OK. + { + text: "some text and \n it's a line break" + }, + { + variable: "o" + } + ); +EOD; + + echo $script; +} + +function display_foo { +?> + +<%= _.escape(name) %>', { name: 'John Smith' }); // OK. + +var html = _.template( + "
    The \"<% __p+=_.escape(o.text) %>\" is the same
    " + // OK. + "as the \"<%= _.escape(o.text) %>\" and the same
    " + // OK. + "as the \"<%- o.text %>\"
    ", // OK. + { + text: "some text and \n it's a line break" + }, + { + variable: "o" + } +); From d9317e5ab9d661c123812205f9480e47e77b1544 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 31 Oct 2020 19:01:07 +0100 Subject: [PATCH 062/129] Security/Underscorejs: ignore Gruntfile.js files These are configuration files and not part of the production code. This check does not verify whether this file is in the project root as we don't know what the project root is. It will plainly ignore any file called `Gruntfile.js` in a case-insensitive manner. Includes unit tests. --- .../Sniffs/Security/UnderscorejsSniff.php | 10 ++ .../Tests/Security/Gruntfile.js | 100 ++++++++++++++++++ .../Tests/Security/UnderscorejsUnitTest.php | 15 +++ 3 files changed, 125 insertions(+) create mode 100644 WordPressVIPMinimum/Tests/Security/Gruntfile.js diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index 2bd7da70..f5459e72 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -61,6 +61,16 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { + /* + * Ignore Gruntfile.js files as they are configuration, not code. + */ + $file_name = $this->strip_quotes( $this->phpcsFile->getFileName() ); + $file_name = strtolower( basename( $file_name ) ); + + if ( $file_name === 'gruntfile.js' ) { + return; + } + /* * Check for delimiter change in JS files. */ diff --git a/WordPressVIPMinimum/Tests/Security/Gruntfile.js b/WordPressVIPMinimum/Tests/Security/Gruntfile.js new file mode 100644 index 00000000..b423d7af --- /dev/null +++ b/WordPressVIPMinimum/Tests/Security/Gruntfile.js @@ -0,0 +1,100 @@ + +module.exports = function(grunt) { + + require('load-grunt-tasks')(grunt); + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + checktextdomain: { + options:{ + text_domain: '<%= pkg.name %>', + correct_domain: true, + keywords: [ + '__:1,2d', + '_e:1,2d', + '_x:1,2c,3d', + 'esc_html__:1,2d', + 'esc_html_e:1,2d', + 'esc_html_x:1,2c,3d', + 'esc_attr__:1,2d', + 'esc_attr_e:1,2d', + 'esc_attr_x:1,2c,3d', + '_ex:1,2c,3d', + '_n:1,2,4d', + '_nx:1,2,4c,5d', + '_n_noop:1,2,3d', + '_nx_noop:1,2,3c,4d' + ] + }, + files: { + src: [ + '**/*.php', + ], + expand: true + } + }, + + makepot: { + target: { + options: { + domainPath: '/languages/', // Where to save the POT file. + mainFile: 'style.css', // Main project file. + potFilename: '<%= pkg.name %>.pot', // Name of the POT file. + type: 'wp-theme', // Type of project (wp-plugin or wp-theme). + processPot: function( pot, options ) { + pot.headers['plural-forms'] = 'nplurals=2; plural=n != 1;'; + pot.headers['x-poedit-basepath'] = '.\n'; + pot.headers['x-poedit-language'] = 'English\n'; + pot.headers['x-poedit-country'] = 'UNITED STATES\n'; + pot.headers['x-poedit-sourcecharset'] = 'utf-8\n'; + pot.headers['X-Poedit-KeywordsList'] = '__;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;\n'; + pot.headers['x-textdomain-support'] = 'yes\n'; + return pot; + } + } + } + }, + + // Clean up build directory + clean: { + main: ['build/<%= pkg.name %>'] + }, + + // Copy the theme into the build directory + copy: { + main: { + src: [ + '**', + '!build/**', + '!.git/**', + '!Gruntfile.js', + '!package.json', + '!.gitignore', + '!.gitmodules', + ], + dest: 'build/<%= pkg.name %>/' + } + }, + + //Compress build directory into .zip and -.zip + compress: { + main: { + options: { + mode: 'zip', + archive: './build/<%= pkg.name %>.zip' + }, + expand: true, + cwd: 'build/<%= pkg.name %>/', + src: ['**/*'], + dest: '<%= pkg.name %>/' + } + } + + }); + + // Default task(s). + grunt.registerTask( 'build', [ 'clean', 'copy', 'compress' ] ); + grunt.registerTask( 'i18n', [ 'checktextdomain', 'makepot' ] ); +}; diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 21b4f70e..01745499 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -18,6 +18,21 @@ */ class UnderscorejsUnitTest extends AbstractSniffUnitTest { + /** + * Get a list of all test files to check. + * + * @param string $testFileBase The base path that the unit tests files will have. + * + * @return string[] + */ + protected function getTestFiles( $testFileBase ) { + return [ + $testFileBase . 'inc', + $testFileBase . 'js', + __DIR__ . DIRECTORY_SEPARATOR . 'Gruntfile.js', + ]; + } + /** * Returns the lines where errors should occur. * From 09d5b924421a19204f2b84d50b8afa6cae570c8c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 1 Nov 2020 02:37:49 +0100 Subject: [PATCH 063/129] Security/Underscorejs: enhancement - check for print execution statements Add a new check for the JS native `print` command and the `_p+=` variation, when used without being combined with `_.escape()`. Includes unit tests. --- .../Sniffs/Security/UnderscorejsSniff.php | 25 ++++++++++++++++- .../Tests/Security/UnderscorejsUnitTest.inc | 12 ++++++++ .../Tests/Security/UnderscorejsUnitTest.js | 4 +++ .../Tests/Security/UnderscorejsUnitTest.php | 28 +++++++++++-------- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index f5459e72..6ea36135 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -26,6 +26,14 @@ class UnderscorejsSniff extends Sniff { */ const UNESCAPED_INTERPOLATE_REGEX = '`<%=\s*(?:.+?%>|$)`'; + /** + * Regex to match execute notations containing a print command + * and retrieve a code snippet. + * + * @var string + */ + const UNESCAPED_PRINT_REGEX = '`<%\s*(?:print\s*\(.+?\)\s*;|__p\s*\+=.+?)\s*%>`'; + /** * Regex to match the "interpolate" keyword when used to overrule the ERB-style delimiters. * @@ -112,7 +120,8 @@ public function process_token( $stackPtr ) { return; } - $content = $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ); + $content = $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ); + $match_count = preg_match_all( self::UNESCAPED_INTERPOLATE_REGEX, $content, $matches ); if ( $match_count > 0 ) { foreach ( $matches[0] as $match ) { @@ -127,6 +136,20 @@ public function process_token( $stackPtr ) { } } + $match_count = preg_match_all( self::UNESCAPED_PRINT_REGEX, $content, $matches ); + if ( $match_count > 0 ) { + foreach ( $matches[0] as $match ) { + if ( strpos( $match, '_.escape(' ) !== false ) { + continue; + } + + // Underscore.js unescaped output. + $message = 'Found Underscore.js unescaped print execution: "%s".'; + $data = [ $match ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'PrintExecution', $data ); + } + } + if ( $this->phpcsFile->tokenizerType !== 'JS' && preg_match( self::INTERPOLATE_KEYWORD_REGEX, $content ) > 0 ) { diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index f3103f02..addf103d 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -106,3 +106,15 @@ function display_foo { + +"); /* OK */ +var compiled = _.template("<% print('Hello ' + epithet); %>"); /* NOK */ +var compiled = _.template("<% __p+=o.text %>"); /* NOK */ diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 01745499..0c0e08c5 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -53,18 +53,20 @@ public function getWarningList( $testFile = '' ) { switch ( $testFile ) { case 'UnderscorejsUnitTest.inc': return [ - 6 => 1, - 14 => 1, - 22 => 1, - 23 => 1, - 28 => 1, - 32 => 1, - 38 => 3, - 45 => 1, - 46 => 1, - 47 => 1, - 58 => 1, - 60 => 1, + 6 => 1, + 14 => 1, + 22 => 1, + 23 => 1, + 28 => 1, + 32 => 1, + 38 => 3, + 45 => 1, + 46 => 1, + 47 => 1, + 58 => 1, + 60 => 1, + 114 => 1, + 115 => 1, ]; case 'UnderscorejsUnitTest.js': @@ -74,6 +76,8 @@ public function getWarningList( $testFile = '' ) { 7 => 1, 9 => 1, 12 => 1, + 44 => 1, + 45 => 1, ]; default: From b99c31d2b06eb298442924f747e471a60019bf4b Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 23 Nov 2020 12:40:38 -0700 Subject: [PATCH 064/129] Functions/DynamicCalls: Remove the word "blacklisted" and use "disallowed" in its place --- WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 36c92021..c069696f 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -30,7 +30,7 @@ class DynamicCallsSniff extends Sniff { * * @var array */ - private $function_names = [ + private $disallowed_functions = [ 'assert' => true, 'compact' => true, 'extract' => true, @@ -141,7 +141,7 @@ private function collect_variables() { */ $current_var_value = $this->strip_quotes( $this->tokens[ $value_ptr ]['content'] ); - if ( isset( $this->function_names[ $current_var_value ] ) === false ) { + if ( isset( $this->disallowed_functions[ $current_var_value ] ) === false ) { // Text string is not one of the ones we're looking for. return; } @@ -167,7 +167,7 @@ private function find_dynamic_calls() { /* * If variable is not found in our registry of variables, do nothing, as we cannot be - * sure that the function being called is one of the blacklisted ones. + * sure that the function being called is one of the disallowed ones. */ if ( ! isset( $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ) ) { return; From d5528e4c901ed42151e41ec6dbe32babdf38391f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 4 Dec 2020 07:16:31 +0100 Subject: [PATCH 065/129] Travis: add build against PHP 8.0 PHP 8.0 has been branched off two months ago, so `nightly` is now PHP 8.1 and in the mean time PHP 8.0 was released last week. As of today, there is a PHP 8.0 image available on Travis. This PR adds a two new builds against PHP 8.0 to the matrix and, as PHP 8.0 has been released, these builds are not allowed to fail. PHPCS 3.5.7 is the first PHPCS version which is runtime compatible with PHP 8.0. Once the minimum supported PHPCS version of this package has been upped to PHPCS 3.5.7, these builds don't need to be special cased anymore. --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 914d0082..f2fd62b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,8 +67,13 @@ jobs: # Run PHPCS against VIPCS. - ./bin/phpcs - # Builds which need a different distro. + # Builds which need a different distro or specific PHPCS version. - stage: test + - php: 8.0 + env: PHPCS_BRANCH="dev-master" + - php: 8.0 + # PHPCS 3.5.7 is the lowest version of PHPCS which supports PHP 8.0. + env: PHPCS_BRANCH="3.5.7" - php: 5.5 dist: trusty env: PHPCS_BRANCH="dev-master" @@ -98,7 +103,7 @@ before_install: install: - travis_retry composer require squizlabs/php_codesniffer:"$PHPCS_BRANCH" --no-update --no-suggest --no-scripts - | - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then + if [[ $TRAVIS_PHP_VERSION == "nightly" || $TRAVIS_PHP_VERSION == "8.0" ]]; then # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform # requirements to get PHPUnit 7.x to install on nightly. travis_retry composer install --ignore-platform-reqs --no-suggest From 19ba9c83601cd56416e847bc057c2e824aefb27e Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 06:38:08 -0700 Subject: [PATCH 066/129] WordPress.Security.EscapeOutput.OutputNotEscaped: Remove redundant error code from WordPress-VIP-Go ruleset, as it is already being inherited from parent --- WordPress-VIP-Go/ruleset.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 240994bf..aede1d22 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -112,7 +112,6 @@ - warning From a2debb7a94b5569c235ce9c2e55fbcc3c0f75a35 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 06:44:19 -0700 Subject: [PATCH 067/129] WordPress.Security.NonceVerification.NoNonceVerification: Remove outdated reference from WordPress-VIP-Go ruleset and let inheritance from parent --- WordPress-VIP-Go/ruleset.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 240994bf..3ce4a280 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -129,11 +129,6 @@ - - - warning - 10 - warning From 77e07fe8fc7a6d959b60a5eabbf99e838055498f Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 08:10:14 -0700 Subject: [PATCH 068/129] Change reference in test to parent sniff --- WordPress-VIP-Go/ruleset-test.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress-VIP-Go/ruleset-test.inc b/WordPress-VIP-Go/ruleset-test.inc index 4706542e..9996527f 100644 --- a/WordPress-VIP-Go/ruleset-test.inc +++ b/WordPress-VIP-Go/ruleset-test.inc @@ -67,7 +67,7 @@ $external_resource = file_get_contents( $test ); // Warning + Message. $file_content = file_get_contents( 'my-file.svg' ); // Ok. wpcom_vip_file_get_contents( $bar ); // Ok. -// WordPress.Security.NonceVerification.NoNonceVerification +// WordPress.Security.NonceVerification (inherited from parent) function bar_foo() { if ( ! isset( $_POST['test'] ) ) { // Error. return; From 3670a4d74c3f64d83124cbcc2b93625680d8c99a Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 08:23:51 -0700 Subject: [PATCH 069/129] Remove second reference of WordPress.WP.AlternativeFunctions.curl_curl_getinfo --- WordPressVIPMinimum/ruleset.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/WordPressVIPMinimum/ruleset.xml b/WordPressVIPMinimum/ruleset.xml index ea062d2e..0d053505 100644 --- a/WordPressVIPMinimum/ruleset.xml +++ b/WordPressVIPMinimum/ruleset.xml @@ -149,9 +149,6 @@ Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. - - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. - error From 603b88256860ce8079172d6f3deebbc3ad643802 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 08:48:03 -0700 Subject: [PATCH 070/129] WordPress-VIP-Go: Replace WordPress.WP.GlobalVariablesOverride.OverrideProhibited with WordPress.WP.GlobalVariablesOverride.Prohibited --- WordPress-VIP-Go/ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 240994bf..04723b1b 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -223,7 +223,7 @@ 1 - + 3 From 9ae891bda13fddeff1a10834e1c50a277e5d68e7 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:08:09 -0700 Subject: [PATCH 071/129] Removing cruft since it is already a warning in WordPress.WP.AlternativeFunctions.file_system_read --- WordPress-VIP-Go/ruleset.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 240994bf..953b5a0f 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -115,11 +115,9 @@ - warning File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ - warning File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ From 9626635882d7d00937435acffd7c97ac21635fbc Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 11 Jan 2021 11:09:12 -0700 Subject: [PATCH 072/129] Remove warning type since it inherits from parent as "warning" by default --- WordPress-VIP-Go/ruleset.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index a196e658..f7a72fb6 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -120,7 +120,6 @@ File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ - warning %s() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see https://wpvip.com/documentation/vip-go/fetching-remote-data/ From 09336ca5cc1a0578868c4eacd9bc51c5068877e0 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 11 Jan 2021 11:17:27 -0700 Subject: [PATCH 073/129] Remove Batcache references in messaging --- WordPress-VIP-Go/ruleset.xml | 5 ----- .../Sniffs/Functions/RestrictedFunctionsSniff.php | 4 ++-- .../Sniffs/Variables/RestrictedVariablesSniff.php | 2 +- .../Tests/Functions/RestrictedFunctionsUnitTest.inc | 2 +- .../Tests/Functions/RestrictedFunctionsUnitTest.php | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index a196e658..d50d0a10 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -93,19 +93,14 @@ Hiding of admin bar is highly discouraged for user roles of "administrator" and "vip_support" -- if these roles are already excluded, this warning can be ignored. - error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. - error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index 74995407..05cce840 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -310,8 +310,8 @@ public function getGroups() { // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#custom-roles // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#cache-constraints 'cookies' => [ - 'type' => 'warning', - 'message' => 'Due to using Batcache, server side based client related logic will not work, use JS instead.', + 'type' => 'error', + 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', 'functions' => [ 'setcookie', ], diff --git a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php index 7b6d7917..f750f660 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php @@ -57,7 +57,7 @@ public function getGroups() { // @link https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#caching-constraints 'cache_constraints' => [ 'type' => 'warning', - 'message' => 'Due to using Batcache, server side based client related logic will not work, use JS instead.', + 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', 'variables' => [ '$_COOKIE', ], diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc index 7c1ba946..79e3569a 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc @@ -122,7 +122,7 @@ vip_safe_wp_remote_get(); // Ok - VIP recommended version of wp_remote_get(). wp_remote_get( $url ); // Warning. cookie( $_GET['test'] ); // Ok - similarly-named function to setcookie(). -setcookie( 'cookie[three]', 'cookiethree' ); // Warning. +setcookie( 'cookie[three]', 'cookiethree' ); // Error. get_post( 123 ); // Ok - not using get_posts(). wp_get_recent_post(); // Ok - similarly-named function to wp_get_recent_posts(). diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php index 493b9270..ada3fd5e 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php @@ -59,6 +59,7 @@ public function getErrorList() { 101 => 1, 104 => 1, 107 => 1, + 125 => 1, 141 => 1, 142 => 1, 143 => 1, @@ -128,7 +129,6 @@ public function getWarningList() { 118 => 1, 119 => 1, 122 => 1, - 125 => 1, 130 => 1, 131 => 1, 132 => 1, From 09945def2376cdaa04cf1c948d057340cc375715 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 11 Jan 2021 11:19:40 -0700 Subject: [PATCH 074/129] Add back error type since we aren't changing the parent yet --- WordPress-VIP-Go/ruleset.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index d50d0a10..f0584eea 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -96,6 +96,7 @@ 6 + error 6 From dd847171a89a6ab8284edb4de68ac80c0056d6d7 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Thu, 14 Jan 2021 16:04:18 -0700 Subject: [PATCH 075/129] Update doc links to docs.wpvip.com and remove WordPress.com refs --- README.md | 6 +-- WordPress-VIP-Go/ruleset-test.php | 38 +++++++++---------- WordPress-VIP-Go/ruleset.xml | 38 +++++++++---------- .../Functions/RestrictedFunctionsSniff.php | 21 ++++------ .../Sniffs/Hooks/RestrictedHooksSniff.php | 5 +-- .../Sniffs/Performance/NoPagingSniff.php | 2 +- .../Sniffs/Performance/OrderByRandSniff.php | 2 +- .../Sniffs/Performance/WPQueryParamsSniff.php | 7 ++-- .../UserExperience/AdminBarRemovalSniff.php | 2 +- .../Variables/RestrictedVariablesSniff.php | 3 +- WordPressVIPMinimum/ruleset-test.php | 6 +-- WordPressVIPMinimum/ruleset.xml | 32 +++++----------- 12 files changed, 70 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 942cd6c9..8a7c31ae 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ This project contains two rulesets: - `WordPressVIPMinimum` - for use with projects on the (older) WordPress.com VIP platform. - `WordPress-VIP-Go` - for use with projects on the (newer) VIP Go platform. -These rulesets contain only the rules which are considered to be "errors" and "warnings" according to the [WordPress VIP Go documentation](https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/) +These rulesets contain only the rules which are considered to be ["errors"](https://docs.wpvip.com/technical-references/code-review/vip-errors/) and ["warnings"](https://docs.wpvip.com/technical-references/code-review/vip-warnings/) according to the WordPress VIP Go documentation. The rulesets use rules from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (WPCS) project, as well as the [VariableAnalysis](https://github.com/sirbrillig/phpcs-variable-analysis) standard. -Go to https://wpvip.com/documentation/phpcs-review-feedback/ to learn about why violations are flagged as errors vs warnings and what the levels mean. +Go to https://docs.wpvip.com/technical-references/code-review/phpcs-report/ to learn about why violations are flagged as errors vs warnings and what the levels mean. ## Minimal requirements @@ -26,7 +26,7 @@ Go to https://wpvip.com/documentation/phpcs-review-feedback/ to learn about why This will install the latest compatible versions of PHPCS, WPCS and VariableAnalysis and register the external standards with PHP_CodeSniffer. -Please refer to the [installation instructions for installing PHP_CodeSniffer for WordPress.com VIP](https://wpvip.com/documentation/how-to-install-php-code-sniffer-for-wordpress-com-vip/) for more details. +Please refer to the [installation instructions for installing PHP_CodeSniffer for WordPress.com VIP](https://docs.wpvip.com/how-tos/code-review/php_codesniffer/) for more details. As of VIPCS version 2.3.0, there is no need to `require` the [PHP_CodeSniffer Standards Composer Installer Plugin](https://github.com/Dealerdirect/phpcodesniffer-composer-installer) anymore as it is now a requirement of VIPCS itself. diff --git a/WordPress-VIP-Go/ruleset-test.php b/WordPress-VIP-Go/ruleset-test.php index d15f245e..f76b352a 100644 --- a/WordPress-VIP-Go/ruleset-test.php +++ b/WordPress-VIP-Go/ruleset-test.php @@ -243,49 +243,49 @@ ], 'messages' => [ 4 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as delete(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as delete(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 7 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as file_put_contents(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as file_put_contents(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 10 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as flock(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as flock(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 14 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputcsv(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputcsv(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 17 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputs(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputs(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 20 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fwrite(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fwrite(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 23 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as ftruncate(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as ftruncate(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 26 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writable(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writable(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 29 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writeable(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writeable(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 32 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as link(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as link(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 35 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as rename(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as rename(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 38 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as symlink(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as symlink(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 41 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as tempnam(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as tempnam(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 44 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as touch(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as touch(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 47 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as unlink(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as unlink(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 50 => [ 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', @@ -297,13 +297,13 @@ 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', ], 60 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fclose(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fclose(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 63 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fopen(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fopen(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 66 => [ - 'file_get_contents() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see https://wpvip.com/documentation/vip-go/fetching-remote-data/', + 'file_get_contents() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/', ], 90 => [ 'Having more than 100 posts returned per page may lead to severe performance problems.', @@ -321,7 +321,7 @@ 'get_page_by_title() is uncached, please use wpcom_vip_get_page_by_title() instead.', ], 139 => [ - 'get_children() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. More Info: https://wpvip.com/documentation/vip-go/uncached-functions/', + '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 => [ 'url_to_postid() is uncached, please use wpcom_vip_url_to_postid() instead.', diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 2818de73..311f308a 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -10,77 +10,77 @@ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning @@ -110,13 +110,13 @@ --> - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ - %s() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see https://wpvip.com/documentation/vip-go/fetching-remote-data/ + %s() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/ @@ -171,7 +171,7 @@ warning 3 - %s() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. More Info: https://wpvip.com/documentation/vip-go/uncached-functions/ + %s() 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/ 3 diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index 05cce840..bd4bce5a 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -64,7 +64,6 @@ public function getGroups() { 'wpcom_vip_irc', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#flush_rewrite_rules 'flush_rewrite_rules' => [ 'type' => 'error', 'message' => '`%s` should not be used in any normal circumstances in the theme code.', @@ -96,8 +95,7 @@ public function getGroups() { 'dbDelta', ], ], - // @link WordPress.com: https://vip.wordpress.com/documentation/vip/code-review-what-we-look-for/#switch_to_blog - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#switch_to_blog + // @link https://docs.wpvip.com/technical-references/code-review/vip-notices/#h-switch_to_blog 'switch_to_blog' => [ 'type' => 'error', 'message' => '%s() is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.', @@ -119,8 +117,7 @@ public function getGroups() { 'url_to_postid', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#custom-roles - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#custom-roles + // @link https://docs.wpvip.com/how-tos/customize-user-roles/ 'custom_role' => [ 'type' => 'error', 'message' => 'Use wpcom_vip_add_role() instead of %s().', @@ -128,7 +125,6 @@ public function getGroups() { 'add_role', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta 'user_meta' => [ 'type' => 'error', 'message' => '%s() usage is highly discouraged on WordPress.com VIP due to it being a multisite, please see https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta.', @@ -178,8 +174,7 @@ public function getGroups() { 'get_intermediate_image_sizes', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#mobile-detection - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#mobile-detection + // @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-mobile-detection 'wp_is_mobile' => [ 'type' => 'error', 'message' => '%s() found. When targeting mobile visitors, jetpack_is_mobile() should be used instead of wp_is_mobile. It is more robust and works better with full page caching.', @@ -298,8 +293,7 @@ public function getGroups() { 'the_field', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#remote-calls - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#remote-calls + // @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-remote-calls 'wp_remote_get' => [ 'type' => 'warning', 'message' => '%s() is highly discouraged. Please use vip_safe_wp_remote_get() instead which is designed to more gracefully handle failure than wp_remote_get() does.', @@ -307,8 +301,7 @@ public function getGroups() { 'wp_remote_get', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#custom-roles - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#cache-constraints + // @link https://docs.wpvip.com/technical-references/code-review/vip-errors/#h-cache-constraints 'cookies' => [ 'type' => 'error', 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', @@ -319,7 +312,7 @@ public function getGroups() { // @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://wpvip.com/documentation/vip-go/uncached-functions/.', + '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/.', 'functions' => [ 'get_posts', 'wp_get_recent_posts', @@ -328,7 +321,7 @@ public function getGroups() { ], 'create_function' => [ 'type' => 'warning', - 'message' => '%s() is highly discouraged, as it can execute arbritary code (additionally, it\'s deprecated as of PHP 7.2): https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#eval-and-create_function. )', + 'message' => '%s() is highly discouraged, as it can execute arbritary code (additionally, it\'s deprecated as of PHP 7.2): https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-eval-and-create_function. )', 'functions' => [ 'create_function', ], diff --git a/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php index 5b44c2df..43054c2b 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php @@ -53,8 +53,7 @@ class RestrictedHooksSniff extends AbstractFunctionParameterSniff { ], ], 'http_request' => [ - // WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. - // VIP Go: https://vip.wordpress.com/documentation/vip-go/fetching-remote-data/. + // https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. 'type' => 'Warning', 'msg' => 'Please ensure that the timeout being filtered is not greater than 3s since remote requests require the user to wait for completion before the rest of the page will load. Manual inspection required.', 'hooks' => [ @@ -63,7 +62,7 @@ class RestrictedHooksSniff extends AbstractFunctionParameterSniff { ], ], 'robotstxt' => [ - // WordPress.com + VIP Go: https://wpvip.com/documentation/robots-txt/. + // https://docs.wpvip.com/how-tos/modify-the-robots-txt-file/. 'type' => 'Warning', 'msg' => 'Don\'t forget to flush the robots.txt cache by going to Settings > Reading and toggling the privacy settings.', 'hooks' => [ diff --git a/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php b/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php index 124b3aeb..9e23fc4f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php @@ -14,7 +14,7 @@ /** * Flag returning high or infinite posts_per_page. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#no-limit-queries + * @link https://docs.wpvip.com/technical-references/code-review/#no-limit-queries * * @package VIPCS\WordPressVIPMinimum * diff --git a/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php b/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php index 47d3c604..e6e64c6f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php @@ -14,7 +14,7 @@ /** * Flag using orderby => rand. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#order-by-rand + * @link https://docs.wpvip.com/technical-references/code-review/#order-by-rand * * @package VIPCS\WordPressVIPMinimum * diff --git a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php index 3a13ea44..a0608679 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php @@ -51,7 +51,7 @@ public function getGroups() { return [ 'PostNotIn' => [ 'type' => 'warning', - 'message' => 'Using `exclude`, which is subsequently used by `post__not_in`, should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information.', + 'message' => 'Using `exclude`, which is subsequently used by `post__not_in`, should be done with caution, see https://docs.wpvip.com/how-tos/improve-performance-by-removing-usage-of-post__not_in/ for more information.', 'keys' => [ 'exclude', ], @@ -73,15 +73,14 @@ public function process_token( $stackPtr ) { $next_token = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_EQUAL, T_CLOSE_SQUARE_BRACKET, T_DOUBLE_ARROW ] ), $stackPtr + 1, null, true ); if ( $this->tokens[ $next_token ]['code'] === T_TRUE ) { - // WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/uncached-functions/. - // VIP Go: https://wpvip.com/documentation/vip-go/uncached-functions/. + // https://docs.wpvip.com/technical-references/caching/uncached-functions/ $message = 'Setting `suppress_filters` to `true` is prohibited.'; $this->phpcsFile->addError( $message, $stackPtr, 'SuppressFiltersTrue' ); } } if ( trim( $this->tokens[ $stackPtr ]['content'], '\'' ) === 'post__not_in' ) { - $message = 'Using `post__not_in` should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information.'; + $message = 'Using `post__not_in` should be done with caution, see https://docs.wpvip.com/how-tos/improve-performance-by-removing-usage-of-post__not_in/ for more information.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'PostNotIn' ); } diff --git a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php index 6945c2f4..c322e923 100644 --- a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php +++ b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php @@ -15,7 +15,7 @@ /** * Discourages removal of the admin bar. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#removing-the-admin-bar + * @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-removing-the-admin-bar * * @package VIPCS\WordPressVIPMinimum * diff --git a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php index f750f660..13000249 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php @@ -37,7 +37,6 @@ class RestrictedVariablesSniff extends AbstractVariableRestrictionsSniff { */ public function getGroups() { return [ - // @link https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta 'user_meta' => [ 'type' => 'error', 'message' => 'Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.', @@ -54,7 +53,7 @@ public function getGroups() { ], ], - // @link https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#caching-constraints + // @link https://docs.wpvip.com/technical-references/code-review/vip-errors/#h-cache-constraints 'cache_constraints' => [ 'type' => 'warning', 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', diff --git a/WordPressVIPMinimum/ruleset-test.php b/WordPressVIPMinimum/ruleset-test.php index 4c9f073e..00ce92e8 100644 --- a/WordPressVIPMinimum/ruleset-test.php +++ b/WordPressVIPMinimum/ruleset-test.php @@ -309,13 +309,13 @@ '`eval()` is a security risk, please refrain from using it.', ], 242 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 243 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 244 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 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.', diff --git a/WordPressVIPMinimum/ruleset.xml b/WordPressVIPMinimum/ruleset.xml index 0d053505..2ce2511c 100644 --- a/WordPressVIPMinimum/ruleset.xml +++ b/WordPressVIPMinimum/ruleset.xml @@ -1,11 +1,6 @@ WordPress VIP Minimum Coding Standards - - - @@ -57,15 +52,13 @@ - - + error `eval()` is a security risk, please refrain from using it. - - + @@ -108,14 +101,12 @@ error - - + - - + error @@ -123,12 +114,9 @@ error - - - - - - + + + @@ -141,13 +129,13 @@ - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. From 8028bd47cfaf4c0e06a2d759676ee55f2cc422b0 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Thu, 14 Jan 2021 16:08:06 -0700 Subject: [PATCH 076/129] Add back period due to sniff requiring inline comment to have full stops --- WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php index a0608679..9b15ef63 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php @@ -73,7 +73,7 @@ public function process_token( $stackPtr ) { $next_token = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_EQUAL, T_CLOSE_SQUARE_BRACKET, T_DOUBLE_ARROW ] ), $stackPtr + 1, null, true ); if ( $this->tokens[ $next_token ]['code'] === T_TRUE ) { - // https://docs.wpvip.com/technical-references/caching/uncached-functions/ + // https://docs.wpvip.com/technical-references/caching/uncached-functions/. $message = 'Setting `suppress_filters` to `true` is prohibited.'; $this->phpcsFile->addError( $message, $stackPtr, 'SuppressFiltersTrue' ); } From 53469f5f82c53a02a8992913c029a3710d09c3b7 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 18 Jan 2021 14:51:34 -0700 Subject: [PATCH 077/129] Exclude VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable due to noise --- WordPress-VIP-Go/ruleset.xml | 3 --- WordPressVIPMinimum/ruleset.xml | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 311f308a..2fa908c8 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -234,9 +234,6 @@ 3 - - 1 - 3 diff --git a/WordPressVIPMinimum/ruleset.xml b/WordPressVIPMinimum/ruleset.xml index 2ce2511c..8dfa7a50 100644 --- a/WordPressVIPMinimum/ruleset.xml +++ b/WordPressVIPMinimum/ruleset.xml @@ -144,7 +144,9 @@ - + + + From 288159abf5681c35ff8c4e26154f6578851b6aa4 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 22 Jan 2021 11:55:04 -0700 Subject: [PATCH 078/129] Silence rather than exclude for more user customization --- WordPress-VIP-Go/ruleset.xml | 3 +++ WordPressVIPMinimum/ruleset.xml | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 2fa908c8..9cca8ee8 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -257,6 +257,9 @@ + + 0 + 0 diff --git a/WordPressVIPMinimum/ruleset.xml b/WordPressVIPMinimum/ruleset.xml index 8dfa7a50..2ce2511c 100644 --- a/WordPressVIPMinimum/ruleset.xml +++ b/WordPressVIPMinimum/ruleset.xml @@ -144,9 +144,7 @@ - - - + From d0915607f7fc0c52439f7b0502d3833bc29aa1db Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:10:07 -0700 Subject: [PATCH 079/129] Remove extra tabs that snuck in the XML file --- WordPress-VIP-Go/ruleset.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 67381d5d..05aa4e35 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -257,8 +257,8 @@ - - 0 + + 0 From c1c62f198615bfb70b380f7c7948ebabd9b69e54 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 2 Mar 2021 19:21:29 +0100 Subject: [PATCH 080/129] CI: fix build failures [1] PR 618 updated the message type for the `WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie` error code from `warning` to `error`. However, the ruleset test for the `WordPressVIPMinimum` ruleset was not updated to match. Fixed now. --- WordPressVIPMinimum/ruleset-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressVIPMinimum/ruleset-test.php b/WordPressVIPMinimum/ruleset-test.php index 00ce92e8..45f428a8 100644 --- a/WordPressVIPMinimum/ruleset-test.php +++ b/WordPressVIPMinimum/ruleset-test.php @@ -164,6 +164,7 @@ 393 => 1, 394 => 1, 395 => 1, + 402 => 1, 415 => 1, 425 => 1, 451 => 1, @@ -269,7 +270,6 @@ 399 => 1, 400 => 1, 401 => 1, - 402 => 1, 403 => 1, 404 => 1, 405 => 1, From 87ddd1b95475d1189d4fe77cbd12b6c1b99175bb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 2 Mar 2021 19:29:09 +0100 Subject: [PATCH 081/129] CI: fix build failures [2] PR 620 silenced the `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable` error code in the VIP Go ruleset. However, the ruleset test for the `WordPress-VIP-Go` ruleset was not updated to match. Fixed now. --- WordPress-VIP-Go/ruleset-test.inc | 4 ++-- WordPress-VIP-Go/ruleset-test.php | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/WordPress-VIP-Go/ruleset-test.inc b/WordPress-VIP-Go/ruleset-test.inc index 9996527f..6000c1fa 100644 --- a/WordPress-VIP-Go/ruleset-test.inc +++ b/WordPress-VIP-Go/ruleset-test.inc @@ -214,7 +214,7 @@ function foo_bar_bar() { // VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable function foo_bar_foo() { - $a = 'Hello'; // Warning + $a = 'Hello'; // OK. Unused variables warning silenced. } // WordPressVIPMinimum.UserExperience.AdminBarRemoval @@ -417,7 +417,7 @@ the_sub_field( 'field' ); // Warning. the_field( 'field' ); // Warning. wp_remote_get( $url ); // Warning. get_posts(); // Warning. -function test_function( $a, $b ) { +function test_function( $a, $b ) { // OK. Unused variables warning silenced. return create_function( '$a, $b', 'return ( $b / $a ); '); // Warning. } wpcom_vip_get_term_link(); // Warning. diff --git a/WordPress-VIP-Go/ruleset-test.php b/WordPress-VIP-Go/ruleset-test.php index f76b352a..12128b66 100644 --- a/WordPress-VIP-Go/ruleset-test.php +++ b/WordPress-VIP-Go/ruleset-test.php @@ -184,7 +184,6 @@ 207 => 1, 208 => 1, 212 => 1, - 217 => 1, 221 => 1, 223 => 1, 225 => 1, @@ -222,7 +221,6 @@ 417 => 1, 418 => 1, 419 => 1, - 420 => 2, 421 => 1, 423 => 1, 424 => 1, From fff8cb0f9cfd74c3f694794de5d33b3b0e6c8e4f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 2 Mar 2021 20:44:41 +0100 Subject: [PATCH 082/129] CI: fix test failure for the VIPCS VariableAnalysis sniff PR 450 deprecated the VIPCS native VariableAnalysis sniff in favour of using the upstream `VariableAnalysis` standard. While the `process()` method - after throwing deprecation notices - would correctly hand off to the `parent::process()` method to let the upstream sniff handle throwing the errors, the `register()` method did not defer to the parent method. This meant that the sniff would only listen to the original tokens the VIPCS native sniff was previously listening too and process those. At the time of the switch-over, the tokens the VIPCS sniff was listening to and the tokens the VA sniff was listening to, happened to be the same. However, in the mean time, in particularly in the VA `2.10.0` release, the tokens the VA sniff is listening to have been updated, while the VIPCS sniff was not updated to match. In practice, that meant that the VIPCS sniff was "missing" some tokens, and therefore not receiving them to `process()`. One of the checks affected, luckily, was covered by a unit test in the VIPCS sniff, which means we have now caught this issue, which could otherwise have remained in the codebase until the VIPCS native sniff was removed. Fixed now by letting the (deprecated) VIPCS native version of the sniff defer to the upstream parent for the `register()` method as well (by removing the method in the VIPCS sniff so the call will fall through to the parent sniff). --- .../Sniffs/Variables/VariableAnalysisSniff.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php index 1296ea31..cac9c9cd 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php @@ -46,21 +46,6 @@ class VariableAnalysisSniff extends \VariableAnalysis\Sniffs\CodeAnalysis\Variab 'FoundPropertyForDeprecatedSniff' => false, ]; - /** - * Returns an array of tokens this test wants to listen for. - * - * @return int[] - */ - public function register() { - return [ - T_VARIABLE, - T_DOUBLE_QUOTED_STRING, - T_HEREDOC, - T_CLOSE_CURLY_BRACKET, - T_STRING, - ]; - } - /** * Don't use. * From 009aca0fde85503c14437f725ffb3057c1f89189 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 2 Mar 2021 15:15:11 +0100 Subject: [PATCH 083/129] QA: remove unused `use` statements --- WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php | 1 - WordPressVIPMinimum/Sniffs/JS/WindowSniff.php | 1 - 2 files changed, 2 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php b/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php index b140d8f6..8017fe58 100644 --- a/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php @@ -7,7 +7,6 @@ namespace WordPressVIPMinimum\Sniffs\JS; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; diff --git a/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php b/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php index 602d342c..e233e479 100644 --- a/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php @@ -7,7 +7,6 @@ namespace WordPressVIPMinimum\Sniffs\JS; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; From d66899c889562bb73186d5010ce7463f5468d023 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Oct 2020 02:34:01 +0200 Subject: [PATCH 084/129] Documentation: various minor improvements ... picked up along the way. Fixes: * A few typos. * Alignment of parameter descriptions. * Wrong example code for some sniffs implementing an abstract sniff. * A warning about commented out code which would start showing once PHPCS 3.6.0 will be released. --- .../Sniffs/Functions/CheckReturnValueSniff.php | 2 +- .../Sniffs/Functions/RestrictedFunctionsSniff.php | 2 +- .../Sniffs/Hooks/AlwaysReturnInFilterSniff.php | 8 ++++---- .../Sniffs/Hooks/PreGetPostsSniff.php | 4 ++-- .../Sniffs/JS/StringConcatSniff.php | 4 ++-- .../Sniffs/Performance/RegexpCompareSniff.php | 14 ++++++-------- .../Performance/RemoteRequestTimeoutSniff.php | 14 ++++++-------- .../Security/ProperEscapingFunctionSniff.php | 2 +- .../Sniffs/UserExperience/AdminBarRemovalSniff.php | 4 ++-- .../Sniffs/Variables/VariableAnalysisSniff.php | 2 +- tests/RulesetTest.php | 8 ++++---- 11 files changed, 30 insertions(+), 34 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php index 9c7c31db..86a7348e 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php @@ -291,7 +291,7 @@ public function findNonCheckedVariables( $stackPtr ) { * Function used as as callback for the array_reduce call. * * @param string $carry The final string. - * @param array $item Processed item. + * @param array $item Processed item. * * @return string */ diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index bd4bce5a..2d8d8ac5 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -396,7 +396,7 @@ public function is_targetted_token( $stackPtr ) { if ( isset( $skipped[ $this->tokens[ $prev ]['code'] ] ) ) { return false; } - // Skip namespaced functions, ie: \foo\bar() not \bar(). + // Skip namespaced functions, ie: `\foo\bar()` not `\bar()`. if ( $this->tokens[ $prev ]['code'] === \T_NS_SEPARATOR ) { $pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $prev - 1, null, true ); if ( $pprev !== false && $this->tokens[ $pprev ]['code'] === \T_STRING ) { diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index 55249970..8029e732 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -122,8 +122,8 @@ private function processArray( $stackPtr ) { * Process string. * * @param int $stackPtr The position in the stack where the token was found. - * @param int $start The start of the token. - * @param int $end The end of the token. + * @param int $start The start of the token. + * @param int $end The end of the token. */ private function processString( $stackPtr, $start = 0, $end = null ) { @@ -149,8 +149,8 @@ private function processString( $stackPtr, $start = 0, $end = null ) { * Process function. * * @param int $stackPtr The position in the stack where the token was found. - * @param int $start The start of the token. - * @param int $end The end of the token. + * @param int $start The start of the token. + * @param int $end The end of the token. */ private function processFunction( $stackPtr, $start = 0, $end = null ) { diff --git a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php index a7d4ed6e..75b54729 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php @@ -195,7 +195,7 @@ private function processClosure( $stackPtr ) { /** * Process function's body * - * @param int $stackPtr The position in the stack where the token was found. + * @param int $stackPtr The position in the stack where the token was found. * @param string $variableName Variable name. */ private function processFunctionBody( $stackPtr, $variableName ) { @@ -363,7 +363,7 @@ private function isEarlyMainQueryCheck( $stackPtr ) { * Is the current code a WP_Query call? * * @param int $stackPtr The position in the stack where the token was found. - * @param null $method Method. + * @param null $method Method. * * @return bool */ diff --git a/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php b/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php index d0ace2ee..74fab5fc 100644 --- a/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php @@ -65,8 +65,8 @@ public function process_token( $stackPtr ) { /** * Consolidated violation. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. - * @param array $data Replacements for the error message. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param array $data Replacements for the error message. */ private function addFoundError( $stackPtr, array $data ) { $message = 'HTML string concatenation detected, this is a security risk, use DOM node construction or a templating language instead: %s.'; diff --git a/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php b/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php index 9e828677..dea5fd5f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php @@ -18,16 +18,14 @@ class RegexpCompareSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Groups of variables to restrict. - * This should be overridden in extending classes. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array diff --git a/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php b/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php index e0c17f95..974532f4 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php @@ -18,16 +18,14 @@ class RemoteRequestTimeoutSniff extends AbstractArrayAssignmentRestrictionsSniff /** * Groups of variables to restrict. - * This should be overridden in extending classes. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index fe973be1..f661c7f9 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -146,7 +146,7 @@ public function is_html_attr( $content ) { * A helper function which tests whether string ends with some other. * * @param string $haystack String which is being tested. - * @param string $needle The substring, which we try to locate on the end of the $haystack. + * @param string $needle The substring, which we try to locate on the end of the $haystack. * * @return bool True if haystack ends with needle. */ diff --git a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php index c322e923..6b985f07 100644 --- a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php +++ b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php @@ -319,7 +319,7 @@ public function process_text_for_style( $stackPtr, $file_name ) { /** * Processes this test for T_STYLE tokens in CSS files. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. * * @return void */ @@ -370,7 +370,7 @@ protected function process_css_style( $stackPtr ) { /** * Consolidated violation. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. */ private function addHidingDetectedError( $stackPtr ) { $message = 'Hiding of the admin bar is not allowed.'; diff --git a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php index cac9c9cd..18e1ed27 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php @@ -17,7 +17,7 @@ use PHP_CodeSniffer\Files\File; /** - * Checks the for undefined function variables. + * Checks for undefined function variables. * * This sniff checks that all function variables * are defined in the function body. diff --git a/tests/RulesetTest.php b/tests/RulesetTest.php index 23ea2921..2cdc883f 100644 --- a/tests/RulesetTest.php +++ b/tests/RulesetTest.php @@ -158,7 +158,7 @@ private function collect_phpcs_result() { /** * Process the Decoded JSON output from PHP_CodeSniffer. * - * @param stdClass $output Deconded JSON output from PHP_CodeSniffer. + * @param \stdClass $output Decoded JSON output from PHP_CodeSniffer. */ private function process_output( $output ) { foreach ( $output->files as $file ) { @@ -310,9 +310,9 @@ private function check_messages() { * Print out the message reporting found issues. * * @param int $expected Expected number of issues. - * @param string $type The type of the issue. - * @param int $number Real number of issues. - * @param int $line Line number. + * @param string $type The type of the issue. + * @param int $number Real number of issues. + * @param int $line Line number. */ private function error_warning_message( $expected, $type, $number, $line ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped From 658854159efcaa8dffa2f7a5000291c680531485 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Tue, 23 Feb 2021 14:57:32 -0700 Subject: [PATCH 085/129] ProperEscapingFunction: Flag when esc_attr is being used outside of HTML attributes --- .../Security/ProperEscapingFunctionSniff.php | 24 ++++++++++-- .../ProperEscapingFunctionUnitTest.inc | 37 +++++++++++++++---- .../ProperEscapingFunctionUnitTest.php | 2 + 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index fe973be1..f56ee1e1 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -26,6 +26,7 @@ class ProperEscapingFunctionSniff extends Sniff { public $escaping_functions = [ 'esc_url', 'esc_attr', + 'esc_attr__', 'esc_html', ]; @@ -53,8 +54,27 @@ public function process_token( $stackPtr ) { $function_name = $this->tokens[ $stackPtr ]['content']; - $echo_or_string_concat = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); + $data = [ $function_name ]; + if ( $function_name === 'esc_attr' || $function_name === 'esc_attr__' ) { + // Find esc_attr being used between HTML tags. + $tokens = array_merge( + Tokens::$emptyTokens, + [ + 'T_ECHO' => T_ECHO, + 'T_OPEN_TAG' => T_OPEN_TAG, + ] + ); + $html_tag = $this->phpcsFile->findPrevious( $tokens, $stackPtr - 1, null, true ); + if ( $this->tokens[ $html_tag ]['type'] === 'T_INLINE_HTML' && false !== strpos( $this->tokens[ $html_tag ]['content'], '>' ) ) { + $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; + $this->phpcsFile->addError( $message, $html_tag, 'notAttrEscAttr', $data ); + return; + } + } + + $echo_or_string_concat = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); + if ( $this->tokens[ $echo_or_string_concat ]['code'] === T_ECHO ) { // Very likely inline HTML with phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_string_concat - 1, null, true ); @@ -80,8 +100,6 @@ public function process_token( $stackPtr ) { return; } - $data = [ $function_name ]; - if ( $function_name !== 'esc_url' && $this->attr_expects_url( $this->tokens[ $html ]['content'] ) ) { $message = 'Wrong escaping function. href, src, and action attributes should be escaped by `esc_url()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'hrefSrcEscUrl', $data ); diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc index 415ebd90..93a3c2ea 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc @@ -1,8 +1,8 @@ '; // NOK. +echo ''; // Error. -echo ""; // NOK. +echo ""; // Error. echo ''; // OK. @@ -12,15 +12,15 @@ echo ''; // OK. echo ""; // OK. -echo ''; // NOK. +echo ''; // Error. -echo ""; // NOK. +echo ""; // Error. ?> -Hello +Hello -Hey +Hey @@ -30,12 +30,33 @@ echo ""; // NOK. echo ''; // OK. -echo ''; // NOK. +echo ''; // Error. echo 'data-param-url="' . esc_url( $share_url ) . '"'; // OK. -echo 'data-param-url="' . esc_html( $share_url ) . '"'; // NOK. +echo 'data-param-url="' . esc_html( $share_url ) . '"'; // Error. ?>
    + +?> + + + + + +
    +Test +
    + +

    \ No newline at end of file diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php index bf47a11e..397ac3d8 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php @@ -34,6 +34,8 @@ public function getErrorList() { 33 => 1, 37 => 1, 41 => 1, + 45 => 1, + 62 => 1, ]; } From baf9e3c6ccd84ef77333eed0298f74f5733cb391 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Fri, 26 Feb 2021 15:24:56 -0700 Subject: [PATCH 086/129] Re-factor logic to be less redundant and account for comma as a string concatenation operator --- .../Security/ProperEscapingFunctionSniff.php | 62 ++++++++++--------- .../ProperEscapingFunctionUnitTest.inc | 5 +- .../ProperEscapingFunctionUnitTest.php | 3 + 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index f56ee1e1..b4e02765 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -24,10 +24,15 @@ class ProperEscapingFunctionSniff extends Sniff { * @var array */ public $escaping_functions = [ - 'esc_url', - 'esc_attr', - 'esc_attr__', - 'esc_html', + 'esc_url' => 'url', + 'esc_attr' => 'attr', + 'esc_attr__' => 'attr', + 'esc_attr_x' => 'attr', + 'esc_attr_e' => 'attr', + 'esc_html' => 'html', + 'esc_html__' => 'html', + 'esc_html_x' => 'html', + 'esc_html_e' => 'html', ]; /** @@ -48,7 +53,7 @@ public function register() { */ public function process_token( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['content'], $this->escaping_functions, true ) === false ) { + if ( isset( $this->escaping_functions[ $this->tokens[ $stackPtr ]['content'] ] ) === false ) { return; } @@ -56,28 +61,11 @@ public function process_token( $stackPtr ) { $data = [ $function_name ]; - if ( $function_name === 'esc_attr' || $function_name === 'esc_attr__' ) { - // Find esc_attr being used between HTML tags. - $tokens = array_merge( - Tokens::$emptyTokens, - [ - 'T_ECHO' => T_ECHO, - 'T_OPEN_TAG' => T_OPEN_TAG, - ] - ); - $html_tag = $this->phpcsFile->findPrevious( $tokens, $stackPtr - 1, null, true ); - if ( $this->tokens[ $html_tag ]['type'] === 'T_INLINE_HTML' && false !== strpos( $this->tokens[ $html_tag ]['content'], '>' ) ) { - $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; - $this->phpcsFile->addError( $message, $html_tag, 'notAttrEscAttr', $data ); - return; - } - } + $echo_or_concat_or_html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); - $echo_or_string_concat = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); - - if ( $this->tokens[ $echo_or_string_concat ]['code'] === T_ECHO ) { + if ( $this->tokens[ $echo_or_concat_or_html ]['code'] === T_ECHO ) { // Very likely inline HTML with phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_string_concat - 1, null, true ); + $php_open = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_concat_or_html - 1, null, true ); if ( $this->tokens[ $php_open ]['code'] !== T_OPEN_TAG ) { return; @@ -85,14 +73,18 @@ public function process_token( $stackPtr ) { $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $php_open - 1, null, true ); - if ( $this->tokens[ $html ]['code'] !== T_INLINE_HTML ) { + if ( $this->tokens[ $html ]['code'] === T_INLINE_HTML && $this->is_outside_html_attr_context( $function_name, $this->tokens[ $html ]['content'] ) ) { + $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; + $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); return; } - } elseif ( $this->tokens[ $echo_or_string_concat ]['code'] === T_STRING_CONCAT ) { + } elseif ( $this->tokens[ $echo_or_concat_or_html ]['code'] === T_STRING_CONCAT || $this->tokens[ $echo_or_concat_or_html ]['code'] === T_COMMA ) { // Very likely string concatenation mixing strings and functions/variables. - $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_string_concat - 1, null, true ); + $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_concat_or_html - 1, null, true ); - if ( $this->tokens[ $html ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { + if ( $this->tokens[ $html ]['code'] === T_CONSTANT_ENCAPSED_STRING && $this->is_outside_html_attr_context( $function_name, trim( $this->tokens[ $html ]['content'], '"\'' ) ) ) { + $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; + $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); return; } } else { @@ -171,4 +163,16 @@ public function is_html_attr( $content ) { public function endswith( $haystack, $needle ) { return substr( $haystack, -strlen( $needle ) ) === $needle; } + + /** + * Tests whether provided string ends with closing HTML tag in an attribute context. + * + * @param string $function_name Escaping attribute function name. + * @param string $content Haystack in which we look for open HTML tag. + * + * @return bool True if string ends with open HTML tag. + */ + public function is_outside_html_attr_context( $function_name, $content ) { + return $this->escaping_functions[ $function_name ] === 'attr' && substr( $content, -1 ) === '>'; + } } diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc index 93a3c2ea..89517681 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc @@ -59,4 +59,7 @@ h1 { Test -

    \ No newline at end of file +

    ?> +' . esc_attr__( $some_var, 'domain' ) . ''; // Error. +echo '

    ', esc_attr__( $title, 'domain' ), '

    '; // Error. +echo '

    ', esc_attr__( $title, 'domain' ) . '

    '; // Error. \ No newline at end of file diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php index 397ac3d8..cf4dbe6e 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php @@ -36,6 +36,9 @@ public function getErrorList() { 41 => 1, 45 => 1, 62 => 1, + 63 => 1, + 64 => 1, + 65 => 1, ]; } From 4a5f5060d12efb04a8d8af3f9fbdc61ef3f22e46 Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:01:39 -0700 Subject: [PATCH 087/129] Re-factor v2 with feedback from @jrfnl --- .../Security/ProperEscapingFunctionSniff.php | 58 +++++++++---------- .../ProperEscapingFunctionUnitTest.inc | 7 ++- .../ProperEscapingFunctionUnitTest.php | 3 + 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index b4e02765..b75fb325 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -35,12 +35,28 @@ class ProperEscapingFunctionSniff extends Sniff { 'esc_html_e' => 'html', ]; + /** + * List of tokens we can skip. + * + * @var array + */ + private $echo_or_concat_tokens = + [ + T_ECHO => T_ECHO, + T_OPEN_TAG => T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO => T_OPEN_TAG_WITH_ECHO, + T_STRING_CONCAT => T_STRING_CONCAT, + T_COMMA => T_COMMA, + ]; + /** * Returns an array of tokens this test wants to listen for. * * @return array */ public function register() { + $this->echo_or_concat_tokens += Tokens::$emptyTokens; + return [ T_STRING ]; } @@ -59,36 +75,17 @@ public function process_token( $stackPtr ) { $function_name = $this->tokens[ $stackPtr ]['content']; - $data = [ $function_name ]; - - $echo_or_concat_or_html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); + $html = $this->phpcsFile->findPrevious( $this->echo_or_concat_tokens, $stackPtr - 1, null, true ); - if ( $this->tokens[ $echo_or_concat_or_html ]['code'] === T_ECHO ) { - // Very likely inline HTML with phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_concat_or_html - 1, null, true ); - - if ( $this->tokens[ $php_open ]['code'] !== T_OPEN_TAG ) { - return; - } + $data = [ $function_name ]; - $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $php_open - 1, null, true ); + if ( isset( Tokens::$textStringTokens[ $this->tokens[ $html ]['code'] ] ) === false ) { // Use $textStringTokens b/c heredoc and nowdoc tokens shouldn't be matched anyways. + return; + } - if ( $this->tokens[ $html ]['code'] === T_INLINE_HTML && $this->is_outside_html_attr_context( $function_name, $this->tokens[ $html ]['content'] ) ) { - $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; - $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); - return; - } - } elseif ( $this->tokens[ $echo_or_concat_or_html ]['code'] === T_STRING_CONCAT || $this->tokens[ $echo_or_concat_or_html ]['code'] === T_COMMA ) { - // Very likely string concatenation mixing strings and functions/variables. - $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_concat_or_html - 1, null, true ); - - if ( $this->tokens[ $html ]['code'] === T_CONSTANT_ENCAPSED_STRING && $this->is_outside_html_attr_context( $function_name, trim( $this->tokens[ $html ]['content'], '"\'' ) ) ) { - $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; - $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); - return; - } - } else { - // Neither - bailing. + if ( $this->is_outside_html_attr_context( $function_name, Sniff::strip_quotes( $this->tokens[ $html ]['content'] ) ) ) { + $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; + $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); return; } @@ -97,6 +94,7 @@ public function process_token( $stackPtr ) { $this->phpcsFile->addError( $message, $stackPtr, 'hrefSrcEscUrl', $data ); return; } + if ( $function_name === 'esc_html' && $this->is_html_attr( $this->tokens[ $html ]['content'] ) ) { $message = 'Wrong escaping function. HTML attributes should be escaped by `esc_attr()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'htmlAttrNotByEscHTML', $data ); @@ -167,12 +165,12 @@ public function endswith( $haystack, $needle ) { /** * Tests whether provided string ends with closing HTML tag in an attribute context. * - * @param string $function_name Escaping attribute function name. - * @param string $content Haystack in which we look for open HTML tag. + * @param string $function_name Name of function found. + * @param string $content Haystack in which we look for open HTML tag. * * @return bool True if string ends with open HTML tag. */ public function is_outside_html_attr_context( $function_name, $content ) { - return $this->escaping_functions[ $function_name ] === 'attr' && substr( $content, -1 ) === '>'; + return $this->escaping_functions[ $function_name ] === 'attr' && substr( trim( $content ), -1 ) === '>'; } } diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc index 89517681..ce9294d2 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc @@ -62,4 +62,9 @@ Test

    ?> ' . esc_attr__( $some_var, 'domain' ) . ''; // Error. echo '

    ', esc_attr__( $title, 'domain' ), '

    '; // Error. -echo '

    ', esc_attr__( $title, 'domain' ) . '

    '; // Error. \ No newline at end of file +echo '

    ', esc_attr__( $title, 'domain' ) . '

    '; // Error. +?> +

    '; ?> // Error. +
    +
    +
    \ No newline at end of file diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php index cf4dbe6e..d47ee570 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php @@ -39,6 +39,9 @@ public function getErrorList() { 63 => 1, 64 => 1, 65 => 1, + 67 => 1, + 68 => 1, + 69 => 1, ]; } From 62ae8108434ab64f52cd5a4b10a9eaee8429cc7d Mon Sep 17 00:00:00 2001 From: Rebecca Hum <16962021+rebeccahum@users.noreply.github.com> Date: Tue, 2 Mar 2021 10:32:35 -0700 Subject: [PATCH 088/129] Fixed descriptions, account for returns better, moved code in a more logical order and added more tests --- .../Security/ProperEscapingFunctionSniff.php | 31 ++++++++++++------- .../ProperEscapingFunctionUnitTest.inc | 11 ++++--- .../ProperEscapingFunctionUnitTest.php | 3 ++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index b75fb325..f0b2eb75 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -73,29 +73,36 @@ public function process_token( $stackPtr ) { return; } - $function_name = $this->tokens[ $stackPtr ]['content']; - $html = $this->phpcsFile->findPrevious( $this->echo_or_concat_tokens, $stackPtr - 1, null, true ); + // Use $textStringTokens b/c heredoc and nowdoc tokens shouldn't be matched anyways. + if ( false === $html || false === isset( Tokens::$textStringTokens[ $this->tokens[ $html ]['code'] ] ) ) { + return; + } + + $function_name = $this->tokens[ $stackPtr ]['content']; + $data = [ $function_name ]; - if ( isset( Tokens::$textStringTokens[ $this->tokens[ $html ]['code'] ] ) === false ) { // Use $textStringTokens b/c heredoc and nowdoc tokens shouldn't be matched anyways. - return; + $content = $this->tokens[ $html ]['content']; + + if ( $this->tokens[ $html ]['code'] !== 'T_INLINE_HTML' ) { + $content = Sniff::strip_quotes( $content ); } - if ( $this->is_outside_html_attr_context( $function_name, Sniff::strip_quotes( $this->tokens[ $html ]['content'] ) ) ) { - $message = 'Wrong escaping function, please do not use `%s()` in a context outside of HTML attributes.'; + if ( $this->is_outside_html_attr_context( $function_name, $content ) ) { + $message = 'Wrong escaping function, using `%s()` in a context outside of HTML attributes may not escape properly.'; $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); return; } - if ( $function_name !== 'esc_url' && $this->attr_expects_url( $this->tokens[ $html ]['content'] ) ) { + if ( $function_name !== 'esc_url' && $this->attr_expects_url( $content ) ) { $message = 'Wrong escaping function. href, src, and action attributes should be escaped by `esc_url()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'hrefSrcEscUrl', $data ); return; } - if ( $function_name === 'esc_html' && $this->is_html_attr( $this->tokens[ $html ]['content'] ) ) { + if ( $function_name === 'esc_html' && $this->is_html_attr( $content ) ) { $message = 'Wrong escaping function. HTML attributes should be escaped by `esc_attr()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'htmlAttrNotByEscHTML', $data ); return; @@ -163,12 +170,12 @@ public function endswith( $haystack, $needle ) { } /** - * Tests whether provided string ends with closing HTML tag in an attribute context. + * Tests whether string ends with opening HTML tag for detection in attribute escaping. * - * @param string $function_name Name of function found. - * @param string $content Haystack in which we look for open HTML tag. + * @param string $function_name Name of function. + * @param string $content Haystack where we look for the end of opening HTML tag. * - * @return bool True if string ends with open HTML tag. + * @return bool True if escaping attribute function and string ends with opening HTML tag. */ public function is_outside_html_attr_context( $function_name, $content ) { return $this->escaping_functions[ $function_name ] === 'attr' && substr( trim( $content ), -1 ) === '>'; diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc index ce9294d2..d77e49db 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc @@ -45,8 +45,8 @@ echo 'data-param-url="' . esc_html( $share_url ) . '"'; // Error. -