diff --git a/composer.json b/composer.json index 5db47da8..a7584826 100644 --- a/composer.json +++ b/composer.json @@ -7,5 +7,14 @@ "gettext/translator": "^1.1", "jaybizzle/crawler-detect": "^1.2", "slim/psr7": "^1.6" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "wp-coding-standards/wpcs": "^3.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/composer.lock b/composer.lock index 4a8a8410..1667a502 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e9f274cb680a99cb3a744f784d9c166d", + "content-hash": "13979facffb09d73589c505f59cc11d5", "packages": [ { "name": "composer/ca-bundle", - "version": "1.3.6", + "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.6" + "source": "https://github.com/composer/ca-bundle/tree/1.3.7" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2023-06-06T12:02:59+00:00" + "time": "2023-08-30T09:31:38+00:00" }, { "name": "embed/embed", @@ -1031,16 +1031,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -1049,7 +1049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1094,7 +1094,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -1110,10 +1110,348 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "98bcdbacbda14b1db85f710b1853125726795bbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/98bcdbacbda14b1db85f710b1853125726795bbc", + "reference": "98bcdbacbda14b1db85f710b1853125726795bbc", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "time": "2023-08-26T04:46:45+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "time": "2023-07-16T21:39:41+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2023-02-22T23:07:41+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.1.0", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "type": "custom" + } + ], + "time": "2023-09-14T07:06:09+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 5490b88d..7e7f96e8 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -8,6 +8,43 @@ $baseDir = dirname($vendorDir); return array( 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', + 'PHPCSUtils\\BackCompat\\BCFile' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', + 'PHPCSUtils\\BackCompat\\BCTokens' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', + 'PHPCSUtils\\BackCompat\\Helper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', + 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', + 'PHPCSUtils\\Exceptions\\TestFileNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', + 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', + 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', + 'PHPCSUtils\\Fixers\\SpacesFixer' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', + 'PHPCSUtils\\Internal\\Cache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrList' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', + 'PHPCSUtils\\Internal\\NoFileCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', + 'PHPCSUtils\\Internal\\StableCollections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', + 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', + 'PHPCSUtils\\Tokens\\Collections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', + 'PHPCSUtils\\Tokens\\TokenHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', + 'PHPCSUtils\\Utils\\Arrays' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', + 'PHPCSUtils\\Utils\\Conditions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', + 'PHPCSUtils\\Utils\\Context' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', + 'PHPCSUtils\\Utils\\ControlStructures' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', + 'PHPCSUtils\\Utils\\FunctionDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', + 'PHPCSUtils\\Utils\\GetTokensAsString' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', + 'PHPCSUtils\\Utils\\Lists' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', + 'PHPCSUtils\\Utils\\MessageHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', + 'PHPCSUtils\\Utils\\Namespaces' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', + 'PHPCSUtils\\Utils\\NamingConventions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', + 'PHPCSUtils\\Utils\\Numbers' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', + 'PHPCSUtils\\Utils\\ObjectDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', + 'PHPCSUtils\\Utils\\Operators' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', + 'PHPCSUtils\\Utils\\Orthography' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', + 'PHPCSUtils\\Utils\\Parentheses' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', + 'PHPCSUtils\\Utils\\PassedParameters' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', + 'PHPCSUtils\\Utils\\Scopes' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', + 'PHPCSUtils\\Utils\\TextStrings' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', + 'PHPCSUtils\\Utils\\UseStatements' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', + 'PHPCSUtils\\Utils\\Variables' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 282c2bdf..9aa04e9f 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -11,6 +11,7 @@ return array( 'Qferrer\\Mjml\\' => array($vendorDir . '/qferr/mjml-php/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'), 'ML\\JsonLD\\' => array($vendorDir . '/ml/json-ld'), 'Jaybizzle\\CrawlerDetect\\' => array($vendorDir . '/jaybizzle/crawler-detect/src'), 'HtmlParser\\' => array($vendorDir . '/oscarotero/html-parser/src'), diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 4f32f1a3..7efec187 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -26,6 +26,7 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1 array ( 'Psr\\Http\\Message\\' => 17, 'Psr\\Http\\Client\\' => 16, + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 57, ), 'M' => array ( @@ -81,6 +82,10 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1 array ( 0 => __DIR__ . '/..' . '/psr/http-client/src', ), + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => + array ( + 0 => __DIR__ . '/..' . '/dealerdirect/phpcodesniffer-composer-installer/src', + ), 'ML\\JsonLD\\' => array ( 0 => __DIR__ . '/..' . '/ml/json-ld', @@ -133,6 +138,43 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1 public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', + 'PHPCSUtils\\BackCompat\\BCFile' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', + 'PHPCSUtils\\BackCompat\\BCTokens' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', + 'PHPCSUtils\\BackCompat\\Helper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', + 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', + 'PHPCSUtils\\Exceptions\\TestFileNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', + 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', + 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', + 'PHPCSUtils\\Fixers\\SpacesFixer' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', + 'PHPCSUtils\\Internal\\Cache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrList' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', + 'PHPCSUtils\\Internal\\NoFileCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', + 'PHPCSUtils\\Internal\\StableCollections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', + 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', + 'PHPCSUtils\\Tokens\\Collections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', + 'PHPCSUtils\\Tokens\\TokenHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', + 'PHPCSUtils\\Utils\\Arrays' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', + 'PHPCSUtils\\Utils\\Conditions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', + 'PHPCSUtils\\Utils\\Context' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', + 'PHPCSUtils\\Utils\\ControlStructures' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', + 'PHPCSUtils\\Utils\\FunctionDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', + 'PHPCSUtils\\Utils\\GetTokensAsString' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', + 'PHPCSUtils\\Utils\\Lists' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', + 'PHPCSUtils\\Utils\\MessageHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', + 'PHPCSUtils\\Utils\\Namespaces' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', + 'PHPCSUtils\\Utils\\NamingConventions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', + 'PHPCSUtils\\Utils\\Numbers' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', + 'PHPCSUtils\\Utils\\ObjectDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', + 'PHPCSUtils\\Utils\\Operators' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', + 'PHPCSUtils\\Utils\\Orthography' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', + 'PHPCSUtils\\Utils\\Parentheses' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', + 'PHPCSUtils\\Utils\\PassedParameters' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', + 'PHPCSUtils\\Utils\\Scopes' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', + 'PHPCSUtils\\Utils\\TextStrings' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', + 'PHPCSUtils\\Utils\\UseStatements' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', + 'PHPCSUtils\\Utils\\Variables' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', diff --git a/vendor/composer/ca-bundle/res/cacert.pem b/vendor/composer/ca-bundle/res/cacert.pem index 6b93dc34..9551dfd8 100644 --- a/vendor/composer/ca-bundle/res/cacert.pem +++ b/vendor/composer/ca-bundle/res/cacert.pem @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue May 30 03:12:04 2023 GMT +## Certificate data from Mozilla as of: Tue Aug 22 03:12:04 2023 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: c47475103fb05bb562bbadff0d1e72346b03236154e1448a6ca191b740f83507 +## SHA256: 0ff137babc6a5561a9cfbe9f29558972e5b528202681b7d3803d03a3e82922bd ## @@ -3222,55 +3222,6 @@ AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- -E-Tugra Global Root CA RSA v3 -============================= ------BEGIN CERTIFICATE----- -MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ -BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb -BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290 -IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU -UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF -LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg -djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx -jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL -sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF -/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q -QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw -bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6 -04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB -eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM -bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg -h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1 -LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ -gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4 -38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q -ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s -SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY -sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl -DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X -nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH -IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX -YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ ------END CERTIFICATE----- - -E-Tugra Global Root CA ECC v3 -============================= ------BEGIN CERTIFICATE----- -MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV -BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB -IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP -MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 -Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 -w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 -Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ -zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W -Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 ------END CERTIFICATE----- - Security Communication RootCA3 ============================== -----BEGIN CERTIFICATE----- @@ -3361,3 +3312,140 @@ SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== -----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index cf7401f2..fdeafbbd 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.3.6", - "version_normalized": "1.3.6.0", + "version": "1.3.7", + "version_normalized": "1.3.7.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, - "time": "2023-06-06T12:02:59+00:00", + "time": "2023-08-30T09:31:38+00:00", "type": "library", "extra": { "branch-alias": { @@ -61,7 +61,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.6" + "source": "https://github.com/composer/ca-bundle/tree/1.3.7" }, "funding": [ { @@ -79,6 +79,87 @@ ], "install-path": "./ca-bundle" }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "time": "2023-01-05T11:28:13+00:00", + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "install-path": "../dealerdirect/phpcodesniffer-composer-installer" + }, { "name": "embed/embed", "version": "v4.4.8", @@ -725,6 +806,148 @@ }, "install-path": "../oscarotero/html-parser" }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "98bcdbacbda14b1db85f710b1853125726795bbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/98bcdbacbda14b1db85f710b1853125726795bbc", + "reference": "98bcdbacbda14b1db85f710b1853125726795bbc", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "time": "2023-08-26T04:46:45+00:00", + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "install-path": "../phpcsstandards/phpcsextra" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.8", + "version_normalized": "1.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + }, + "time": "2023-07-16T21:39:41+00:00", + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "install-path": "../phpcsstandards/phpcsutils" + }, { "name": "psr/http-client", "version": "1.0.2", @@ -1075,28 +1298,88 @@ "install-path": "../slim/psr7" }, { - "name": "symfony/polyfill-php80", - "version": "v1.27.0", - "version_normalized": "1.27.0.0", + "name": "squizlabs/php_codesniffer", + "version": "3.7.2", + "version_normalized": "3.7.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "time": "2023-02-22T23:07:41+00:00", + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "install-path": "../squizlabs/php_codesniffer" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2022-11-03T14:55:06+00:00", + "time": "2023-01-26T09:26:14+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1142,7 +1425,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -1159,8 +1442,83 @@ } ], "install-path": "../symfony/polyfill-php80" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.0.1", + "version_normalized": "3.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.1.0", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "time": "2023-09-14T07:06:09+00:00", + "type": "phpcodesniffer-standard", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "type": "custom" + } + ], + "install-path": "../wp-coding-standards/wpcs" } ], "dev": true, - "dev-package-names": [] + "dev-package-names": [ + "dealerdirect/phpcodesniffer-composer-installer", + "phpcsstandards/phpcsextra", + "phpcsstandards/phpcsutils", + "squizlabs/php_codesniffer", + "wp-coding-standards/wpcs" + ] } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 4a68b3a2..c4f6ea49 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '6d20c0b45ad6539a4835855d6a33eab9f232cea1', + 'reference' => 'f79071d77a00dcf2cb86e6e7011443c8b50bac7c', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,21 +13,30 @@ '__root__' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '6d20c0b45ad6539a4835855d6a33eab9f232cea1', + 'reference' => 'f79071d77a00dcf2cb86e6e7011443c8b50bac7c', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/ca-bundle' => array( - 'pretty_version' => '1.3.6', - 'version' => '1.3.6.0', - 'reference' => '90d087e988ff194065333d16bc5cf649872d9cdb', + 'pretty_version' => '1.3.7', + 'version' => '1.3.7.0', + 'reference' => '76e46335014860eec1aa5a724799a00a2e47cc85', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), 'dev_requirement' => false, ), + 'dealerdirect/phpcodesniffer-composer-installer' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'reference' => '4be43904336affa5c2f70744a348312336afd0da', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'embed/embed' => array( 'pretty_version' => 'v4.4.8', 'version' => '4.4.8.0', @@ -118,6 +127,24 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'phpcsstandards/phpcsextra' => array( + 'pretty_version' => '1.1.1', + 'version' => '1.1.1.0', + 'reference' => '98bcdbacbda14b1db85f710b1853125726795bbc', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcsstandards/phpcsextra', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcsstandards/phpcsutils' => array( + 'pretty_version' => '1.0.8', + 'version' => '1.0.8.0', + 'reference' => '69465cab9d12454e5e7767b9041af0cd8cd13be7', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcsstandards/phpcsutils', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'psr/http-client' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', @@ -184,14 +211,32 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'squizlabs/php_codesniffer' => array( + 'pretty_version' => '3.7.2', + 'version' => '3.7.2.0', + 'reference' => 'ed8e00df0a83aa96acf703f8c2979ff33341f879', + 'type' => 'library', + 'install_path' => __DIR__ . '/../squizlabs/php_codesniffer', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.27.0', - 'version' => '1.27.0.0', - 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '6caa57379c4aec19c0a12a38b59b26487dcfe4b5', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), + 'wp-coding-standards/wpcs' => array( + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', + 'reference' => 'b4caf9689f1a0e4a4c632679a44e638c1c67aff1', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../wp-coding-standards/wpcs', + 'aliases' => array(), + 'dev_requirement' => true, + ), ), ); diff --git a/vendor/phpcsstandards/phpcsextra/CHANGELOG.md b/vendor/phpcsstandards/phpcsextra/CHANGELOG.md new file mode 100644 index 00000000..262b97d6 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/CHANGELOG.md @@ -0,0 +1,523 @@ +# Change Log for the PHPCSExtra standard for PHP CodeSniffer + +All notable changes to this project will be documented in this file. + +This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses [Semantic Versioning](http://semver.org/). + +**Legend**: +:wrench: = Includes auto-fixer. +:bar_chart: = Includes metrics. +:books: = Includes CLI documentation. + + +## [Unreleased] + +_Nothing yet._ + +## [1.1.1] - 2023-08-26 + +### Changed + +#### Modernize + +* `Modernize.FunctionCalls.Dirname`: the sniff will now respect a potentially set [`php_version` configuration option][php_version-config] and only report on modernizations which are possible on the configured `php_version`. [#261] + If the `php_version` is not set, the sniff will continue to report on all modernization options. + +#### Other + +* Various documentation improvements. Props in part to [@szepeviktor]. +* Improved defensive coding in select places. +* Various housekeeping. + +[#261]: https://github.com/PHPCSStandards/PHPCSExtra/pull/261 + + +## [1.1.0] - 2023-07-19 + +### Added + +#### Universal + +* :wrench: :books: New `Universal.CodeAnalysis.NoEchoSprintf` sniff to detect use of the inefficient `echo [v]sprintf(...);` combi and recommends using `[v]printf()` instead. [#242] +* :bar_chart: :books: New `Universal.FunctionDeclarations.NoLongClosures` sniff to detect "long" closures and recommend using a named function instead. [#240] + The sniff offers the following properties to influence its behaviour: `recommendedLines` (defaults to `5`), `maxLines` (defaults to `8`), `ignoreCommentLines` (defaults to `true`) and `ignoreEmptyLines` (defaults to `true`). +* :wrench: :bar_chart: :books: New `Universal.FunctionDeclarations.RequireFinalMethodsInTraits` sniff to enforce non-private, non-abstract methods in traits to be declared as `final`. [#243], [#245] + There is a separate `NonFinalMagicMethodFound` error code for magic methods to allow those to be excluded from the check. +* :wrench: :bar_chart: :books: New `Universal.UseStatements.DisallowMixedGroupUse` sniff to disallow group use statements which import a combination of namespace/OO construct, functions and/or constants in one statement. [#241], [#246] + Note: the fixer will use a semi-standardized format for group use statements. If there are more specific requirements for the formatting of group use statements, the ruleset configurator should ensure that additional sniffs are included in the ruleset to enforce the required format. +* :wrench: :bar_chart: :books: New `Universal.UseStatements.KeywordSpacing` sniff to enforce the use of a single space after the `use`, `function`, `const` keywords and both before and after the `as` keyword in import `use` statements. [#247] + The sniff has modular error codes to allow for disabling individual checks. +* :wrench: :books: New `Universal.UseStatements.NoUselessAliases` sniff to detect useless aliases (aliasing something to its original name) in import use statements. [#244] + Note: as OO and function names in PHP are case-insensitive, aliasing to the same name, using a different case is also considered useless. +* :wrench: :bar_chart: :books: New `Universal.WhiteSpace.CommaSpacing` sniff to enforce that there is no space before a comma and exactly one space, or a new line, after a comma. [#254] + Additionally, the sniff also enforces that the comma should follow the code and not be placed after a trailing comment. + The sniff has modular error codes to allow for disabling individual checks and checks in certain contexts. + The sniff will respect a potentially set [`php_version` configuration option][php_version-config] when deciding how to handle the spacing after a heredoc/nowdoc closer. + +### Changed + +#### Universal + +* Minor performance improvements for the `Universal.Arrays.DuplicateArrayKey` and the `Universal.CodeAnalysis.ConstructorDestructorReturn` sniffs. [#251], [#252] + +#### Other + +* Composer: The minimum `PHPCSUtils` requirement has been updated to `^1.0.8` (was `^1.0.6`). [#249], [#254] +* Various housekeeping. + +[#240]: https://github.com/PHPCSStandards/PHPCSExtra/pull/240 +[#241]: https://github.com/PHPCSStandards/PHPCSExtra/pull/241 +[#242]: https://github.com/PHPCSStandards/PHPCSExtra/pull/242 +[#243]: https://github.com/PHPCSStandards/PHPCSExtra/pull/243 +[#244]: https://github.com/PHPCSStandards/PHPCSExtra/pull/244 +[#245]: https://github.com/PHPCSStandards/PHPCSExtra/pull/245 +[#246]: https://github.com/PHPCSStandards/PHPCSExtra/pull/246 +[#247]: https://github.com/PHPCSStandards/PHPCSExtra/pull/247 +[#249]: https://github.com/PHPCSStandards/PHPCSExtra/pull/249 +[#251]: https://github.com/PHPCSStandards/PHPCSExtra/pull/251 +[#252]: https://github.com/PHPCSStandards/PHPCSExtra/pull/252 +[#254]: https://github.com/PHPCSStandards/PHPCSExtra/pull/254 + + +## [1.0.4] - 2023-06-18 + +### Changed + +#### Other + +* Composer: The minimum `PHPCSUtils` requirement has been updated to `^1.0.6` (was `^1.0.0`). [#237] +* Various housekeeping. + +### Fixed + +#### Universal + +* `Universal.Constants.LowercaseClassResolutionKeyword`: prevent false positives for function calls to methods called `class`. [#226] + +[#226]: https://github.com/PHPCSStandards/PHPCSExtra/pull/226 +[#237]: https://github.com/PHPCSStandards/PHPCSExtra/pull/237 + + +## [1.0.3] - 2023-03-28 + +### Changed + +#### Universal + +* `Universal.WhiteSpace.DisallowInlineTabs`: significant performance improvement. [#216], [#217] + +#### Other + +* Various housekeeping. + +### Fixed + +#### Modernize + +* `Modernize.FunctionCalls.Dirname`: prevent false positives for attribute classes called `dirname`. [#211], [#213] + +[#211]: https://github.com/PHPCSStandards/PHPCSExtra/pull/211 +[#213]: https://github.com/PHPCSStandards/PHPCSExtra/pull/213 +[#216]: https://github.com/PHPCSStandards/PHPCSExtra/pull/216 +[#217]: https://github.com/PHPCSStandards/PHPCSExtra/pull/217 + + +## [1.0.2] - 2023-01-10 + +### Changed + +#### Universal + +* `Universal.CodeAnalysis.ConstructorDestructorReturn`: the sniff will now respect a potentially set [`php_version` configuration option][php_version-config] and only report on PHP4-style constructors when the `php_version` is below `'80000'`. Thanks [@anomiex] for reporting! [#207], [#208] + +[#207]: https://github.com/PHPCSStandards/PHPCSExtra/issues/207 +[#208]: https://github.com/PHPCSStandards/PHPCSExtra/pull/208 + + +## [1.0.1] - 2023-01-05 + +### Fixed + +#### Universal + +* `Universal.CodeAnalysis.ConstructorDestructorReturn`: fixed false positive for return statements in nested functions/closures declared within constructor/destructor methods. Thanks [@anomiex] for reporting! [#201], [#202] + +[#201]: https://github.com/PHPCSStandards/PHPCSExtra/issues/201 +[#202]: https://github.com/PHPCSStandards/PHPCSExtra/pull/202 + + +## [1.0.0] - 2023-01-04 + +:warning: Important: this package now requires [PHPCSUtils 1.0.0]. Please make sure you use `--with-[all-]dependencies` when running `composer update`. :exclamation: + +For the full list of features, please see the changelogs of the alpha/rc releases: +* [1.0.0-rc1](https://github.com/PHPCSStandards/PHPCSExtra/releases/tag/1.0.0-rc1) +* [1.0.0-alpha3](https://github.com/PHPCSStandards/PHPCSExtra/releases/tag/1.0.0-alpha3) +* [1.0.0-alpha2](https://github.com/PHPCSStandards/PHPCSExtra/releases/tag/1.0.0-alpha2) +* [1.0.0-alpha1](https://github.com/PHPCSStandards/PHPCSExtra/releases/tag/1.0.0-alpha1) + +### Changed + +#### Other + +* Updated various sniffs to take advantage of PHPCSUtils 1.0.0(-rc1). [#193], [#194], [#195] +* Minor documentation improvements. +* Various housekeeping. + +### Fixed + +#### Modernize + +* `Modernize.FunctionCalls.Dirname`: the sniff will now correctly recognize magic constants in a case-insensitive manner. [#187] + +[PHPCSUtils 1.0.0]: https://github.com/PHPCSStandards/PHPCSUtils/releases/tag/1.0.0 + +[#187]: https://github.com/PHPCSStandards/PHPCSExtra/pull/187 +[#193]: https://github.com/PHPCSStandards/PHPCSExtra/pull/193 +[#194]: https://github.com/PHPCSStandards/PHPCSExtra/pull/194 +[#195]: https://github.com/PHPCSStandards/PHPCSExtra/pull/195 + + +## [1.0.0-RC1] - 2022-12-07 + +:warning: Important: this package now requires [PHPCSUtils 1.0.0-alpha4]. Please make sure you use `--with-[all-]dependencies` when running `composer update`. :exclamation: + +### Added + +#### Modernize + +* This is a new standard with one sniff to start with. +* :wrench: :books: New `Modernize.FunctionCalls.Dirname` sniff to detect and auto-fix two typical code modernizations which can be made related to the [`dirname()`][php-manual-dirname] function. [#172] + +#### Universal + +* :wrench: :bar_chart: :books: New `Universal.Classes.DisallowAnonClassParentheses` sniff to disallow the use of parentheses when declaring an anonymous class without passing parameters. [#76], [#162] +* :wrench: :bar_chart: :books: New `Universal.Classes.RequireAnonClassParentheses` sniff to require the use of parentheses when declaring an anonymous class, whether parameters are passed or not. [#76], [#166] +* :wrench: :bar_chart: :books: New `Universal.Classes.DisallowFinalClass` sniff to disallow classes being declared `final`. [#108], [#114], [#148], [#163] +* :wrench: :bar_chart: :books: New `Universal.Classes.RequireFinalClass` sniff to require all non-`abstract` classes to be declared `final`. [#109], [#148], [#164] + Warning: the auto-fixer for this sniff _may_ have unintended side-effects for applications and should be used with care! This is considered a _risky_ fixer. +* :wrench: :bar_chart: :books: New `Universal.Classes.ModifierKeywordOrder` sniff to standardize the modifier keyword order for class declarations. [#142] + The sniff offers an `order` property to specify the preferred order. +* :wrench: :books: New `Universal.CodeAnalysis.ConstructorDestructorReturn` sniff to verify that class constructor/destructor methods 1) do not have a return type declaration and 2) do not return a value. [#137], [#140], [#146] Inspired by [@derickr]. +* :wrench: :books: New `Universal.CodeAnalysis.ForeachUniqueAssignment` sniff to detect `foreach` control structures which use the same variable for both the key as well as the value assignment as this will lead to unexpected - and most likely unintended - behaviour. [#110], [#175] + The fixer will maintain the existing behaviour of the code. Mind: this may not be the _intended_ behaviour. +* :wrench: :books: New `Universal.CodeAnalysis.StaticInFinalClass` sniff to detect using `static` instead of `self` in OO constructs which are `final`. [#116], [#180] + The sniff has modular error codes to allow for making exceptions based on the type of use for `static`. +* :wrench: :bar_chart: :books: New `Universal.Constants.LowercaseClassResolutionKeyword` sniff to enforce that the `class` keyword when used for class name resolution, i.e. `::class`, is in lowercase. [#72] +* :wrench: :bar_chart: :books: New `Universal.Constants.ModifierKeywordOrder` sniff to standardize the modifier keyword order for OO constant declarations. [#143] + The sniff offers an `order` property to specify the preferred order. +* :wrench: :books: New `Universal.ControlStructures.DisallowLonelyIf` sniff to disallow `if` statements as the only statement in an `else` block. [#85], [#168], [#169] + Inspired by the [ESLint "no lonely if"] rule. + Note: This sniff will not fix the indentation of the "inner" code. It is strongly recommended to run this sniff together with the `Generic.WhiteSpace.ScopeIndent` sniff to get the correct indentation. +* :bar_chart: :books: New `Universal.Files.SeparateFunctionsFromOO` sniff to enforce that a file should either declare (global/namespaced) functions or declare OO structures, but not both. [#95], [#170], [#171] + Nested function declarations, i.e. functions declared within a function/method will be disregarded for the purposes of this sniff. + The same goes for anonymous classes, closures and arrow functions. +* :books: New `Universal.NamingConventions.NoReservedKeywordParameterNames` sniff to verify that function parameters do not use reserved keywords as names, as this can quickly become confusing when people use them in function calls using named parameters. [#80], [#81], [#106], [#107], [#173] + The sniff has modular error codes to allow for making exceptions for specific keywords. +* :wrench: :bar_chart: :books: New `Universal.Operators.TypeSeparatorSpacing` sniff to enforce no spaces around union type and intersection type separators. [#117] +* :wrench: :books: New `Universal.PHP.OneStatementInShortEchoTag` sniff to disallow short open echo tags `= 8.0][php-rfc-negative_array_index]. [#177], [#178] + If a [`php_version` configuration option][php_version-config] has been passed to PHPCS, it will be respected by the sniff and only report duplicate keys for the configured PHP version. +* `Universal.ControlStructures.DisallowAlternativeSyntax`: the sniff will now also record a metric when single-line (no body) control structures are encountered. [#158] +* `Universal.ControlStructures.DisallowAlternativeSyntax`: the error message thrown by the sniff is now more descriptive. [#159] +* `Universal.ControlStructures.DisallowAlternativeSyntax`: metrics will no longer be recorded for `elseif` and `else` keywords, but only on the `if` keyword as the type of syntax used has to be the same for the whole "chain". [#161] +* `Universal.Lists.DisallowLongListSyntax`: the sniff will no longer record (incomplete) metrics about long vs short list usage. [#155] +* `Universal.Lists.DisallowShortListSyntax`: the sniff will now record (complete) metrics about long vs short list usage. [#155] +* `Universal.OOStructures.AlphabeticExtendsImplements`: documented support for `enum ... implements`. [#150] +* `Universal.UseStatements.DisallowUseClass`: updated error message and metric name to take PHP 8.1 `enum`s into account. [#149] +* `Universal.UseStatements.NoLeadingBackslash`: the sniff will now also flag and auto-fix leading backslashes in group use statements. [#167] + +#### Other +* Updated the sniffs for compatibility with PHPCSUtils 1.0.0-alpha4. [#134] +* Updated the sniffs to correctly handle PHP 8.0/8.1/8.2 features whenever relevant. +* Readme: Updated installation instructions for compatibility with Composer 2.2+. [#101] +* Composer: The minimum `PHP_CodeSniffer` requirement has been updated to `^3.7.1` (was `^3.3.1`). [#115], [#130] +* Composer: The package will now identify itself as a static analysis tool. Thanks [@GaryJones]! [#126] +* All non-`abstract` classes in this package are now `final`. [#121] +* All XML documentation now has a schema annotation. [#128] +* Various housekeeping. + +### Fixed + +The upgrade to PHPCSUtils 1.0.0-alpha4 took care of a number of bugs, which potentially could have affected sniffs in this package. + +#### NormalizedArrays +* `NormalizedArrays.Arrays.ArrayBraceSpacing`: the sniff now allows for trailing comments after the array opener in multi-line arrays. [#118] +* `NormalizedArrays.Arrays.ArrayBraceSpacing`: trailing comments at the end of an array, but before the closer, in multi-line arrays will no longer confuse the sniff. [#135] +* `NormalizedArrays.Arrays.CommaAfterLast`: the fixer will now recognize PHP 7.3+ flexible heredoc/nowdocs and in that case, will add the comma on the same line as the heredoc/nowdoc closer. [#144] + +#### Universal +* `Universal.Arrays.DisallowShortArraySyntax`: nested short arrays in short lists will now be detected and fixed correctly. [#153] +* `Universal.ControlStructures.DisallowAlternativeSyntax`: the sniff will no longer bow out indiscriminately when the `allowWithInlineHTML` property is set to `true`. [#90], [#161] +* `Universal.ControlStructures.DisallowAlternativeSyntax`: when alternative control structure syntax is allowed in combination with inline HTML (`allowWithInlineHTML` property set to `true`), inline HTML in functions declared within the control structure body will no longer be taken into account for determining whether or not the control structure contains inline HTML. [#160] +* `Universal.Lists.DisallowShortListSyntax`: the sniff will work around a tokenizer bug in PHPCS 3.7.1, which previously could lead to false negatives. [#151]. +* `Universal.Lists.DisallowShortListSyntax`: nested short lists in short arrays will now be detected and fixed correctly. [#152] +* `Universal.Operators.DisallowStandalonePostIncrementDecrement`: the sniff will now correctly recognize stand-alone statements which end on a PHP close tag. [#176] + +[#72]: https://github.com/PHPCSStandards/PHPCSExtra/pull/72 +[#76]: https://github.com/PHPCSStandards/PHPCSExtra/pull/76 +[#80]: https://github.com/PHPCSStandards/PHPCSExtra/pull/80 +[#81]: https://github.com/PHPCSStandards/PHPCSExtra/pull/81 +[#85]: https://github.com/PHPCSStandards/PHPCSExtra/pull/85 +[#89]: https://github.com/PHPCSStandards/PHPCSExtra/pull/89 +[#90]: https://github.com/PHPCSStandards/PHPCSExtra/pull/90 +[#95]: https://github.com/PHPCSStandards/PHPCSExtra/pull/95 +[#101]: https://github.com/PHPCSStandards/PHPCSExtra/pull/101 +[#106]: https://github.com/PHPCSStandards/PHPCSExtra/pull/106 +[#107]: https://github.com/PHPCSStandards/PHPCSExtra/pull/107 +[#108]: https://github.com/PHPCSStandards/PHPCSExtra/pull/108 +[#109]: https://github.com/PHPCSStandards/PHPCSExtra/pull/109 +[#110]: https://github.com/PHPCSStandards/PHPCSExtra/pull/110 +[#114]: https://github.com/PHPCSStandards/PHPCSExtra/pull/114 +[#115]: https://github.com/PHPCSStandards/PHPCSExtra/pull/115 +[#116]: https://github.com/PHPCSStandards/PHPCSExtra/pull/116 +[#117]: https://github.com/PHPCSStandards/PHPCSExtra/pull/117 +[#118]: https://github.com/PHPCSStandards/PHPCSExtra/pull/118 +[#119]: https://github.com/PHPCSStandards/PHPCSExtra/pull/119 +[#120]: https://github.com/PHPCSStandards/PHPCSExtra/pull/120 +[#121]: https://github.com/PHPCSStandards/PHPCSExtra/pull/121 +[#122]: https://github.com/PHPCSStandards/PHPCSExtra/pull/122 +[#123]: https://github.com/PHPCSStandards/PHPCSExtra/pull/123 +[#124]: https://github.com/PHPCSStandards/PHPCSExtra/pull/124 +[#126]: https://github.com/PHPCSStandards/PHPCSExtra/pull/126 +[#128]: https://github.com/PHPCSStandards/PHPCSExtra/pull/128 +[#130]: https://github.com/PHPCSStandards/PHPCSExtra/pull/130 +[#134]: https://github.com/PHPCSStandards/PHPCSExtra/pull/134 +[#135]: https://github.com/PHPCSStandards/PHPCSExtra/pull/135 +[#137]: https://github.com/PHPCSStandards/PHPCSExtra/pull/137 +[#140]: https://github.com/PHPCSStandards/PHPCSExtra/pull/140 +[#142]: https://github.com/PHPCSStandards/PHPCSExtra/pull/142 +[#143]: https://github.com/PHPCSStandards/PHPCSExtra/pull/143 +[#144]: https://github.com/PHPCSStandards/PHPCSExtra/pull/144 +[#146]: https://github.com/PHPCSStandards/PHPCSExtra/pull/146 +[#147]: https://github.com/PHPCSStandards/PHPCSExtra/pull/147 +[#148]: https://github.com/PHPCSStandards/PHPCSExtra/pull/148 +[#149]: https://github.com/PHPCSStandards/PHPCSExtra/pull/149 +[#150]: https://github.com/PHPCSStandards/PHPCSExtra/pull/150 +[#151]: https://github.com/PHPCSStandards/PHPCSExtra/pull/151 +[#152]: https://github.com/PHPCSStandards/PHPCSExtra/pull/152 +[#153]: https://github.com/PHPCSStandards/PHPCSExtra/pull/153 +[#154]: https://github.com/PHPCSStandards/PHPCSExtra/pull/154 +[#155]: https://github.com/PHPCSStandards/PHPCSExtra/pull/155 +[#158]: https://github.com/PHPCSStandards/PHPCSExtra/pull/158 +[#159]: https://github.com/PHPCSStandards/PHPCSExtra/pull/159 +[#160]: https://github.com/PHPCSStandards/PHPCSExtra/pull/160 +[#161]: https://github.com/PHPCSStandards/PHPCSExtra/pull/161 +[#162]: https://github.com/PHPCSStandards/PHPCSExtra/pull/162 +[#163]: https://github.com/PHPCSStandards/PHPCSExtra/pull/163 +[#164]: https://github.com/PHPCSStandards/PHPCSExtra/pull/164 +[#165]: https://github.com/PHPCSStandards/PHPCSExtra/pull/165 +[#166]: https://github.com/PHPCSStandards/PHPCSExtra/pull/166 +[#167]: https://github.com/PHPCSStandards/PHPCSExtra/pull/167 +[#168]: https://github.com/PHPCSStandards/PHPCSExtra/pull/168 +[#169]: https://github.com/PHPCSStandards/PHPCSExtra/pull/169 +[#170]: https://github.com/PHPCSStandards/PHPCSExtra/pull/170 +[#171]: https://github.com/PHPCSStandards/PHPCSExtra/pull/171 +[#172]: https://github.com/PHPCSStandards/PHPCSExtra/pull/172 +[#173]: https://github.com/PHPCSStandards/PHPCSExtra/pull/173 +[#175]: https://github.com/PHPCSStandards/PHPCSExtra/pull/175 +[#176]: https://github.com/PHPCSStandards/PHPCSExtra/pull/176 +[#177]: https://github.com/PHPCSStandards/PHPCSExtra/pull/177 +[#178]: https://github.com/PHPCSStandards/PHPCSExtra/pull/178 +[#180]: https://github.com/PHPCSStandards/PHPCSExtra/pull/180 + +[php-manual-dirname]: https://www.php.net/function.dirname +[php-rfc-negative_array_index]: https://wiki.php.net/rfc/negative_array_index +[ESLint "no lonely if"]: https://eslint.org/docs/rules/no-lonely-if +[PHPCSUtils 1.0.0-alpha4]: https://github.com/PHPCSStandards/PHPCSUtils/releases/tag/1.0.0-alpha4 + + +## [1.0.0-alpha3] - 2020-06-29 + +### Added + +#### Universal + +* :wrench: :books: New `Universal.Arrays.DisallowShortArraySyntax` sniff to disallow short array syntax. [#40] + In contrast to the PHPCS native `Generic.Arrays.DisallowShortArraySyntax` sniff, this sniff will ignore short list syntax and not cause parse errors when the fixer is used. +* :wrench: :bar_chart: :books: New `Universal.Constants.UppercaseMagicConstants` sniff to enforce that PHP native magic constants are in uppercase. [#64] +* :bar_chart: :books: New `Universal.Namespaces.DisallowDeclarationWithoutName` sniff to disallow namespace declarations without a namespace name. [#50] +* :bar_chart: :books: New `Universal.Operators.DisallowLogicalAndOr` sniff to enforce the use of the boolean `&&` and `||` operators instead of the logical `and`/`or` operators. [#52] + Note: as the [operator precedence] of the logical operators is significantly lower than the operator precedence of boolean operators, this sniff does not contain an auto-fixer. +* :bar_chart: :books: New `Universal.Operators.DisallowShortTernary` sniff to disallow the use of short ternaries `?:`. [#42] + While short ternaries are useful when used correctly, the principle of them is often misunderstood and they are more often than not used incorrectly, leading to hard to debug issues and/or PHP warnings/notices. +* :wrench: :bar_chart: :books: New `Universal.Operators.DisallowStandalonePostIncrementDecrement` sniff disallow the use of post-in/decrements in stand-alone statements and discourage the use of multiple increment/decrement operators in a stand-alone statement. [#65] +* :wrench: :bar_chart: :books: New `Universal.Operators.StrictComparisons` sniff to enforce the use of strict comparisons. [#48] + Warning: the auto-fixer for this sniff _may_ cause bugs in applications and should be used with care! This is considered a _risky_ fixer. +* :wrench: :bar_chart: :books: New `Universal.OOStructures.AlphabeticExtendsImplements` sniff to verify that the names used in a class "implements" statement or an interface "extends" statement are listed in alphabetic order. [#55] + * This sniff contains a public `orderby` property to determine the sort order to use for the statement. + If all names used are unqualified, the sort order won't make a difference. + However, if one or more of the names are partially or fully qualified, the chosen sort order will determine how the sorting between unqualified, partially and fully qualified names is handled. + The sniff supports two sort order options: + - _'name'_ : sort by the interface name only (default); + - _'full'_ : sort by the full name as used in the statement (without leading backslash). + In both cases, the sorting will be done using natural sort, case-insensitive. + * The sniff has modular error codes to allow for selective inclusion/exclusion: + - `ImplementsWrongOrder` - for "class implements" statements. + - `ImplementsWrongOrderWithComments` - for "class implements" statements interlaced with comments. These will not be auto-fixed. + - `ExtendsWrongOrder` - for "interface extends" statements. + - `ExtendsWrongOrderWithComments` - for "interface extends" statements interlaced with comments. These will not be auto-fixed. + * When fixing, the existing spacing between the names in an `implements`/`extends` statement will not be maintained. + The fixer will separate each name with a comma and one space. + If alternative formatting is desired, a sniff which will check and fix the formatting should be added to the ruleset. +* :wrench: :bar_chart: :books: New `Universal.UseStatements.LowercaseFunctionConst` sniff to enforce that `function` and `const` keywords when used in an import `use` statement are always lowercase. [#58] +* :wrench: :bar_chart: :books: New `Universal.UseStatements.NoLeadingBackslash` sniff to verify that a name being imported in an import `use` statement does not start with a leading backslash. [#46] + Names in import `use` statements should always be fully qualified, so a leading backslash is not needed and it is strongly recommended not to use one. + This sniff handles all types of import use statements supported by PHP, in contrast to other sniffs for the same in, for instance, the PSR12 or the Slevomat standard, which are incomplete. +* :wrench: :books: New `Universal.WhiteSpace.DisallowInlineTabs` sniff to enforce using spaces for mid-line alignment. [#43] + +### Changed + +#### Other +* The `master` branch has been renamed to `stable`. +* Composer: The version requirements for the [Composer PHPCS plugin] have been widened to allow for version 0.7.0 which supports Composer 2.0.0. [#62] +* Various housekeeping. + +[#40]: https://github.com/PHPCSStandards/PHPCSExtra/pull/40 +[#42]: https://github.com/PHPCSStandards/PHPCSExtra/pull/42 +[#43]: https://github.com/PHPCSStandards/PHPCSExtra/pull/43 +[#46]: https://github.com/PHPCSStandards/PHPCSExtra/pull/46 +[#48]: https://github.com/PHPCSStandards/PHPCSExtra/pull/48 +[#50]: https://github.com/PHPCSStandards/PHPCSExtra/pull/50 +[#52]: https://github.com/PHPCSStandards/PHPCSExtra/pull/52 +[#55]: https://github.com/PHPCSStandards/PHPCSExtra/pull/55 +[#58]: https://github.com/PHPCSStandards/PHPCSExtra/pull/58 +[#62]: https://github.com/PHPCSStandards/PHPCSExtra/pull/62 +[#64]: https://github.com/PHPCSStandards/PHPCSExtra/pull/64 +[#65]: https://github.com/PHPCSStandards/PHPCSExtra/pull/65 + +[operator precedence]: https://www.php.net/language.operators.precedence + + +## [1.0.0-alpha2] - 2020-02-18 + +### Added + +#### Universal +* :wrench: :bar_chart: :books: New `Universal.ControlStructures.DisallowAlternativeSyntax` sniff to disallow using the alternative syntax for control structures. [#23] + - This sniff contains a `allowWithInlineHTML` property to allow alternative syntax when inline HTML is used within the control structure. In all other cases, the use of the alternative syntax will still be disallowed. + - The sniff has modular error codes to allow for making exceptions based on specific control structures and/or specific control structures in combination with inline HTML. +* :bar_chart: `Universal.UseStatements.DisallowUseClass/Function/Const`: new, additional metrics about the import source will be shown in the `info` report. [#25] + +#### Other +* Readme: installation instructions and sniff list. [#26] + +### Changed + +#### Universal +* `Universal.Arrays.DuplicateArrayKey`: wording of the error message. [#18] +* `Universal.UseStatements.DisallowUseClass/Function/Const`: the error codes have been made more modular. [#25] + Each of these sniffs now has four additional error codes: + + In all other circumstances, the existing error codes FoundWithAlias and FoundWithoutAlias will continue to be used. + +#### Other +* Improved formatting of the CLI documentation which can be viewed using `--generator=text`. [#17] +* Various housekeeping. + +### Fixed + +#### Universal +* `Universal.Arrays.DuplicateArrayKey`: improved handling of parse errors. [#34] +* `Universal.ControlStructures.IfElseDeclaration`: the fixer will now respect tab indentation. [#19] +* `Universal.UseStatements.DisallowUseClass/Function/Const`: the determination of whether a import is aliased in now done in a case-insensitive manner. [#25] +* `Universal.UseStatements.DisallowUseClass/Function/Const`: an import from the global namespace would previously always be seen as non-aliased, even when it was aliased. [#25] +* `Universal.UseStatements.DisallowUseClass/Function/Const`: improved tolerance for `use` import statements with leading backslashes. [#25] + +[#17]: https://github.com/PHPCSStandards/PHPCSExtra/pull/17 +[#18]: https://github.com/PHPCSStandards/PHPCSExtra/pull/18 +[#19]: https://github.com/PHPCSStandards/PHPCSExtra/pull/19 +[#23]: https://github.com/PHPCSStandards/PHPCSExtra/pull/23 +[#25]: https://github.com/PHPCSStandards/PHPCSExtra/pull/25 +[#26]: https://github.com/PHPCSStandards/PHPCSExtra/pull/26 +[#34]: https://github.com/PHPCSStandards/PHPCSExtra/pull/34 + + +## 1.0.0-alpha1 - 2020-01-23 + +Initial alpha release containing: +* A `NormalizedArrays` standard which will contain a full set of sniffs to check the formatting of array declarations. +* A `Universal` standard which will contain a collection of universal sniffs. + DO NOT INCLUDE THIS AS A STANDARD. + `Universal`, like the upstream PHPCS `Generic` standard, contains sniffs which contradict each other. + Include individual sniffs from this standard in a custom project/company ruleset to use them. + +This initial alpha release contains the following sniffs: + +### NormalizedArrays +* :wrench: :bar_chart: :books: `NormalizedArrays.Arrays.ArrayBraceSpacing`: enforce consistent spacing for the open/close braces of array declarations. + The sniff allows for having different settings for: + - Space between the array keyword and the open parenthesis for long arrays via the `keywordSpacing` property. + Accepted values: (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. + - Spaces on the inside of the braces for empty arrays via the `spacesWhenEmpty` property. + Accepted values: (string) `newline`, (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. + - Spaces on the inside of the braces for single-line arrays via the `spacesSingleLine` property; + Accepted values: (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. + - Spaces on the inside of the braces for multi-line arrays via the `spacesMultiLine` property. + Accepted values: (string) `newline`, (int) number of spaces or `false` to turn this check off. Defaults to `newline`. + Note: if any of the above properties are set to `newline`, it is recommended to also include an array indentation sniff. This sniff will not handle the indentation. +* :wrench: :bar_chart: :books: `NormalizedArrays.Arrays.CommaAfterLast`: enforce/forbid a comma after the last item in an array declaration. + By default, this sniff will: + + This can be changed for each type or array individually by setting the singleLine or multiLine properties in a custom ruleset. + The valid values are: enforce, forbid or skip to not check the comma after the last array item for a particular type of array. + +### Universal +* :books: `Universal.Arrays.DuplicateArrayKey`: detects duplicate array keys in array declarations. +* :books: `Universal.Arrays.MixedArrayKeyTypes`: best practice sniff: don't use a mix of integer and numeric keys for array items. +* :books: `Universal.Arrays.MixedKeyedUnkeyedArray`: best practice sniff: don't use a mix of keyed and unkeyed array items. +* :wrench: :bar_chart: :books: `Universal.ControlStructures.IfElseDeclaration`: verify that else(if) statements with braces are on a new line. +* :wrench: :bar_chart: :books: `Universal.Lists.DisallowLongListSyntax`: disallow the use of long `list`s. +* :wrench: :bar_chart: :books: `Universal.Lists.DisallowShortListSyntax`: disallow the use of short lists. +* :bar_chart: :books: `Universal.Namespaces.DisallowCurlyBraceSyntax`: disallow the use of the alternative namespace declaration syntax using curly braces. +* :bar_chart: :books: `Universal.Namespaces.EnforceCurlyBraceSyntax`: enforce the use of the alternative namespace syntax using curly braces. +* :books: `Universal.Namespaces.OneDeclarationPerFile`: disallow the use of multiple namespaces within a file. +* :bar_chart: :books: `Universal.UseStatements.DisallowUseClass`: forbid using import use statements for classes/traits/interfaces. + Individual sub-types can be allowed by excluding specific error codes. +* :bar_chart: :books: `Universal.UseStatements.DisallowUseConst`: forbid using import use statements for constants. + Individual sub-types can be allowed by excluding specific error codes. +* :bar_chart: :books: `Universal.UseStatements.DisallowUseFunction`: forbid using import use statements for functions. + Individual sub-types can be allowed by excluding specific error codes. + +[Composer PHPCS plugin]: https://github.com/PHPCSStandards/composer-installer +[php_version-config]: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-php-version + +[Unreleased]: https://github.com/PHPCSStandards/PHPCSExtra/compare/stable...HEAD +[1.1.1]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.4...1.1.0 +[1.0.4]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-rc1...1.0.0 +[1.0.0-RC1]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-alpha3...1.0.0-rc1 +[1.0.0-alpha3]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-alpha2...1.0.0-alpha3 +[1.0.0-alpha2]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-alpha1...1.0.0-alpha2 + +[@anomiex]: https://github.com/anomiex +[@derickr]: https://github.com/derickr +[@GaryJones]: https://github.com/GaryJones +[@szepeviktor]: https://github.com/szepeviktor diff --git a/vendor/phpcsstandards/phpcsextra/LICENSE b/vendor/phpcsstandards/phpcsextra/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/phpcsstandards/phpcsextra/Modernize/Docs/FunctionCalls/DirnameStandard.xml b/vendor/phpcsstandards/phpcsextra/Modernize/Docs/FunctionCalls/DirnameStandard.xml new file mode 100644 index 00000000..5f486646 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Modernize/Docs/FunctionCalls/DirnameStandard.xml @@ -0,0 +1,40 @@ + + + + = 5.3: Usage of dirname(__FILE__) can be replaced with __DIR__. + ]]> + + + + __DIR__; + ]]> + + + dirname(__FILE__); + ]]> + + + + = 7.0: Nested calls to dirname() can be replaced by using dirname() with the $levels parameter. + ]]> + + + + dirname($file, 3); + ]]> + + + dirname(dirname(dirname($file))); + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Modernize/Sniffs/FunctionCalls/DirnameSniff.php b/vendor/phpcsstandards/phpcsextra/Modernize/Sniffs/FunctionCalls/DirnameSniff.php new file mode 100644 index 00000000..b064b264 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Modernize/Sniffs/FunctionCalls/DirnameSniff.php @@ -0,0 +1,382 @@ + + */ + public function register() + { + return [\T_STRING]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (isset($this->phpVersion) === false || \defined('PHP_CODESNIFFER_IN_TESTS')) { + // Set default value to prevent this code from running every time the sniff is triggered. + $this->phpVersion = 0; + + $phpVersion = Helper::getConfigData('php_version'); + if ($phpVersion !== null) { + $this->phpVersion = (int) $phpVersion; + } + } + + if ($this->phpVersion !== 0 && $this->phpVersion < 50300) { + // PHP version too low, nothing to do. + return; + } + + $tokens = $phpcsFile->getTokens(); + + if (\strtolower($tokens[$stackPtr]['content']) !== 'dirname') { + // Not our target. + return; + } + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false + || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS + || isset($tokens[$nextNonEmpty]['parenthesis_owner']) === true + ) { + // Not our target. + return; + } + + if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) { + // Live coding or parse error, ignore. + return; + } + + if (Context::inAttribute($phpcsFile, $stackPtr) === true) { + // Class instantiation in attribute, not function call. + return; + } + + // Check if it is really a function call to the global function. + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + if (isset(Collections::objectOperators()[$tokens[$prevNonEmpty]['code']]) === true + || $tokens[$prevNonEmpty]['code'] === \T_NEW + ) { + // Method call, class instantiation or other "not our target". + return; + } + + if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR) { + $prevPrevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevNonEmpty - 1), null, true); + if ($tokens[$prevPrevToken]['code'] === \T_STRING + || $tokens[$prevPrevToken]['code'] === \T_NAMESPACE + ) { + // Namespaced function. + return; + } + } + + /* + * As of here, we can be pretty sure this is a function call to the global function. + */ + $opener = $nextNonEmpty; + $closer = $tokens[$nextNonEmpty]['parenthesis_closer']; + + $parameters = PassedParameters::getParameters($phpcsFile, $stackPtr); + $paramCount = \count($parameters); + if (empty($parameters) || $paramCount > 2) { + // No parameters or too many parameter. + return; + } + + $pathParam = PassedParameters::getParameterFromStack($parameters, 1, 'path'); + if ($pathParam === false) { + // If the path parameter doesn't exist, there's nothing to do. + return; + } + + $levelsParam = PassedParameters::getParameterFromStack($parameters, 2, 'levels'); + if ($levelsParam === false && $paramCount === 2) { + // There must be a typo in the param name or an otherwise stray parameter. Ignore. + return; + } + + /* + * PHP 5.3+: Detect use of dirname(__FILE__). + */ + if (\strtoupper($pathParam['clean']) === '__FILE__') { + $levelsValue = false; + + // Determine if the issue is auto-fixable. + $hasComment = $phpcsFile->findNext(Tokens::$commentTokens, ($opener + 1), $closer); + $fixable = ($hasComment === false); + + if ($fixable === true) { + $levelsValue = $this->getLevelsValue($phpcsFile, $levelsParam); + if ($levelsParam !== false && $levelsValue === false) { + // Can't autofix if we don't know the value of the $levels parameter. + $fixable = false; + } + } + + $error = 'Use the __DIR__ constant instead of calling dirname(__FILE__) (PHP >= 5.3)'; + $code = 'FileConstant'; + + // Throw the error. + if ($fixable === false) { + $phpcsFile->addError($error, $stackPtr, $code); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code); + if ($fix === true) { + if ($levelsParam === false || $levelsValue === 1) { + // No $levels or $levels set to 1: we can replace the complete function call. + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($stackPtr, '__DIR__'); + + for ($i = ($stackPtr + 1); $i <= $closer; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + // Remove potential leading \. + if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR) { + $phpcsFile->fixer->replaceToken($prevNonEmpty, ''); + } + + $phpcsFile->fixer->endChangeset(); + } else { + // We can replace the $path parameter and will need to adjust the $levels parameter. + $filePtr = $phpcsFile->findNext(\T_FILE, $pathParam['start'], ($pathParam['end'] + 1)); + $levelsPtr = $phpcsFile->findNext(\T_LNUMBER, $levelsParam['start'], ($levelsParam['end'] + 1)); + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($filePtr, '__DIR__'); + $phpcsFile->fixer->replaceToken($levelsPtr, ($levelsValue - 1)); + $phpcsFile->fixer->endChangeset(); + } + } + + return; + } + + /* + * PHP 7.0+: Detect use of nested calls to dirname(). + */ + if ($this->phpVersion !== 0 && $this->phpVersion < 70000) { + // No need to check for this issue if the PHP version would not allow for it anyway. + return; + } + + if (\preg_match('`^\s*\\\\?dirname\s*\(`i', $pathParam['clean']) !== 1) { + return; + } + + /* + * Check if there is something _behind_ the nested dirname() call within the same parameter. + * + * Note: the findNext() calls are safe and will always match the dirname() function call + * as otherwise the above regex wouldn't have matched. + */ + $innerDirnamePtr = $phpcsFile->findNext(\T_STRING, $pathParam['start'], ($pathParam['end'] + 1)); + $innerOpener = $phpcsFile->findNext(\T_OPEN_PARENTHESIS, ($innerDirnamePtr + 1), ($pathParam['end'] + 1)); + if (isset($tokens[$innerOpener]['parenthesis_closer']) === false) { + // Shouldn't be possible. + return; // @codeCoverageIgnore + } + + $innerCloser = $tokens[$innerOpener]['parenthesis_closer']; + if ($innerCloser !== $pathParam['end']) { + $hasContentAfter = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($innerCloser + 1), + ($pathParam['end'] + 1), + true + ); + if ($hasContentAfter !== false) { + // Matched code like: `dirname(dirname($file) . 'something')`. Ignore. + return; + } + } + + /* + * Determine if this is an auto-fixable error. + */ + + // Step 1: Are there comments ? If so, not auto-fixable as we don't want to remove comments. + $fixable = true; + $outerLevelsValue = false; + $innerParameters = []; + $innerLevelsParam = false; + $innerLevelsValue = false; + + for ($i = ($opener + 1); $i < $closer; $i++) { + if (isset(Tokens::$commentTokens[$tokens[$i]['code']])) { + $fixable = false; + break; + } + + if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS + && isset($tokens[$i]['parenthesis_closer']) + ) { + // Skip over everything within the nested dirname() function call. + $i = $tokens[$i]['parenthesis_closer']; + } + } + + // Step 2: Does the `$levels` parameter exist for the outer dirname() call and if so, is it usable ? + if ($fixable === true) { + $outerLevelsValue = $this->getLevelsValue($phpcsFile, $levelsParam); + if ($levelsParam !== false && $outerLevelsValue === false) { + // Can't autofix if we don't know the value of the $levels parameter. + $fixable = false; + } + } + + // Step 3: Does the `$levels` parameter exist for the inner dirname() call and if so, is it usable ? + if ($fixable === true) { + $innerParameters = PassedParameters::getParameters($phpcsFile, $innerDirnamePtr); + $innerLevelsParam = PassedParameters::getParameterFromStack($innerParameters, 2, 'levels'); + $innerLevelsValue = $this->getLevelsValue($phpcsFile, $innerLevelsParam); + if ($innerLevelsParam !== false && $innerLevelsValue === false) { + // Can't autofix if we don't know the value of the $levels parameter. + $fixable = false; + } + } + + /* + * Throw the error. + */ + $error = 'Pass the $levels parameter to the dirname() call instead of using nested dirname() calls'; + $error .= ' (PHP >= 7.0)'; + $code = 'Nested'; + + if ($fixable === false) { + $phpcsFile->addError($error, $stackPtr, $code); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code); + if ($fix === false) { + return; + } + + /* + * Fix the error. + */ + $phpcsFile->fixer->beginChangeset(); + + // Remove the info in the _outer_ param call. + for ($i = $opener; $i < $innerOpener; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + for ($i = ($innerCloser + 1); $i <= $closer; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + if ($innerLevelsParam !== false) { + // Inner $levels parameter already exists, just adjust the value. + $innerLevelsPtr = $phpcsFile->findNext( + \T_LNUMBER, + $innerLevelsParam['start'], + ($innerLevelsParam['end'] + 1) + ); + $phpcsFile->fixer->replaceToken($innerLevelsPtr, ($innerLevelsValue + $outerLevelsValue)); + } else { + // Inner $levels parameter does not exist yet. We need to add it. + $content = ', '; + + $prevBeforeCloser = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($innerCloser - 1), null, true); + if ($tokens[$prevBeforeCloser]['code'] === \T_COMMA) { + // Trailing comma found, no need to add the comma. + $content = ' '; + } + + $innerPathParam = PassedParameters::getParameterFromStack($innerParameters, 1, 'path'); + if (isset($innerPathParam['name_token']) === true) { + // Non-named param cannot follow named param, so add param name. + $content .= 'levels: '; + } + + $content .= ($innerLevelsValue + $outerLevelsValue); + $phpcsFile->fixer->addContentBefore($innerCloser, $content); + } + + $phpcsFile->fixer->endChangeset(); + } + + /** + * Determine the value of the $levels parameter passed to dirname(). + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param array|false $levelsParam The information about the parameter as retrieved + * via PassedParameters::getParameterFromStack(). + * + * @return int|false Integer levels value or FALSE if the levels value couldn't be determined. + */ + private function getLevelsValue($phpcsFile, $levelsParam) + { + if ($levelsParam === false) { + return 1; + } + + $ignore = Tokens::$emptyTokens; + $ignore[] = \T_LNUMBER; + + $hasNonNumber = $phpcsFile->findNext($ignore, $levelsParam['start'], ($levelsParam['end'] + 1), true); + if ($hasNonNumber !== false) { + return false; + } + + return (int) $levelsParam['clean']; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Modernize/ruleset.xml b/vendor/phpcsstandards/phpcsextra/Modernize/ruleset.xml new file mode 100644 index 00000000..57e341ba --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Modernize/ruleset.xml @@ -0,0 +1,5 @@ + + + + A collection of sniffs to detect code modernization opportunities. + diff --git a/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml new file mode 100644 index 00000000..e8a006f0 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + (1, 2); + ]]> + + + + + + + + + + + ); + +$args = [ ]; + ]]> + + + + + + + + + + + 1, 2 ); + +$args = [ 1, 2 ]; + ]]> + + + + + + + + + 1, + 2 +); + +$args = [ + 1, + 2 +]; + ]]> + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml new file mode 100644 index 00000000..c5094c5c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml @@ -0,0 +1,43 @@ + + + + no comma after the last array item. + + However, for multi-line arrays, there should be a comma after the last array item. + ]]> + + + + + + + , ); + ]]> + + + + + 'foo', + 2 => 'bar', +]; + ]]> + + + 'foo', + 2 => 'bar' +]; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php new file mode 100644 index 00000000..b6317707 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php @@ -0,0 +1,305 @@ + + */ + public function register() + { + return Collections::arrayOpenTokensBC(); + } + + /** + * Processes this test when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $stackPtr The position in the PHP_CodeSniffer + * file's token stack where the token + * was found. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + /* + * Normalize the public settings. + */ + if ($this->keywordSpacing !== false) { + $this->keywordSpacing = \max((int) $this->keywordSpacing, 0); + } + + if ($this->spacesSingleLine !== false) { + $this->spacesSingleLine = \max((int) $this->spacesSingleLine, 0); + } + + if ($this->spacesMultiLine !== false && $this->spacesMultiLine !== 'newline') { + $this->spacesMultiLine = \max((int) $this->spacesMultiLine, 0); + } + + if ($this->spacesWhenEmpty !== false && $this->spacesWhenEmpty !== 'newline') { + $this->spacesWhenEmpty = \max((int) $this->spacesWhenEmpty, 0); + } + + if ($this->keywordSpacing === false + && $this->spacesSingleLine === false + && $this->spacesMultiLine === false + && $this->spacesWhenEmpty === false + ) { + // Nothing to do. Why was the sniff turned on at all ? + return; + } + + $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr); + if ($openClose === false) { + // Live coding, short list or real square brackets. + return; + } + + $tokens = $phpcsFile->getTokens(); + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + /* + * Check the spacing between the array keyword and the open parenthesis for long arrays. + */ + if ($tokens[$stackPtr]['code'] === \T_ARRAY && $this->keywordSpacing !== false) { + $error = 'There should be %s between the "array" keyword and the open parenthesis. Found: %s'; + $code = 'SpaceAfterKeyword'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $opener, + $this->keywordSpacing, + $error, + $code, + 'error', + 0, + 'Space between array keyword and open brace' + ); + } + + /* + * Check for empty arrays. + */ + $nextNonWhiteSpace = $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true); + if ($nextNonWhiteSpace === $closer) { + if ($this->spacesWhenEmpty === false) { + // Check was turned off. + return; + } + + $error = 'There should be %s between the array opener and closer for an empty array. Found: %s'; + $code = 'EmptyArraySpacing'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $closer, + $this->spacesWhenEmpty, + $error, + $code, + 'error', + 0, + 'Space between open and close brace for an empty array' + ); + + return; + } + + /* + * Check non-empty arrays. + */ + if ($tokens[$opener]['line'] === $tokens[$closer]['line']) { + // Single line array. + if ($this->spacesSingleLine === false) { + // Check was turned off. + return; + } + + $error = 'Expected %s after the array opener in a single line array. Found: %s'; + $code = 'SpaceAfterArrayOpenerSingleLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true), + $this->spacesSingleLine, + $error, + $code, + 'error', + 0, + 'Space after array opener, single line array' + ); + + $error = 'Expected %s before the array closer in a single line array. Found: %s'; + $code = 'SpaceBeforeArrayCloserSingleLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $closer, + $phpcsFile->findPrevious(\T_WHITESPACE, ($closer - 1), null, true), + $this->spacesSingleLine, + $error, + $code, + 'error', + 0, + 'Space before array closer, single line array' + ); + + return; + } + + // Multi-line array. + if ($this->spacesMultiLine === false) { + // Check was turned off. + return; + } + + $error = 'Expected %s after the array opener in a multi line array. Found: %s'; + $code = 'SpaceAfterArrayOpenerMultiLine'; + + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true); + if ($this->spacesMultiLine === 'newline') { + // Check for a trailing comment after the array opener and allow for it. + if (($tokens[$nextNonWhitespace]['code'] === \T_COMMENT + || isset(Tokens::$phpcsCommentTokens[$tokens[$nextNonWhitespace]['code']]) === true) + && $tokens[$nextNonWhitespace]['line'] === $tokens[$opener]['line'] + ) { + // We found a trailing comment after array opener. Treat that as the opener instead. + $opener = $nextNonWhitespace; + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true); + } + } + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $nextNonWhitespace, + $this->spacesMultiLine, + $error, + $code, + 'error', + 0, + 'Space after array opener, multi-line array' + ); + + $error = 'Expected %s before the array closer in a multi line array. Found: %s'; + $code = 'SpaceBeforeArrayCloserMultiLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $closer, + $phpcsFile->findPrevious(\T_WHITESPACE, ($closer - 1), null, true), + $this->spacesMultiLine, + $error, + $code, + 'error', + 0, + 'Space before array closer, multi-line array' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php new file mode 100644 index 00000000..d03d1ffc --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php @@ -0,0 +1,219 @@ + + */ + private $validValues = [ + 'enforce' => true, + 'forbid' => true, + 'skip' => true, + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return Collections::arrayOpenTokensBC(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + // Validate the property input. Invalid values will result in the check being skipped. + if (isset($this->validValues[$this->singleLine]) === false) { + $this->singleLine = 'skip'; + } + if (isset($this->validValues[$this->multiLine]) === false) { + $this->multiLine = 'skip'; + } + + $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr); + if ($openClose === false) { + // Short list, real square bracket, live coding or parse error. + return; + } + + $tokens = $phpcsFile->getTokens(); + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $action = $this->singleLine; + $phrase = 'single-line'; + $errorCode = 'SingleLine'; + if ($tokens[$opener]['line'] !== $tokens[$closer]['line']) { + $action = $this->multiLine; + $phrase = 'multi-line'; + $errorCode = 'MultiLine'; + } + + if ($action === 'skip') { + // Nothing to do. + return; + } + + $lastNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($closer - 1), $opener, true); + if ($lastNonEmpty === false || $lastNonEmpty === $opener) { + // Bow out: empty array. + return; + } + + $isComma = ($tokens[$lastNonEmpty]['code'] === \T_COMMA); + + $phpcsFile->recordMetric( + $stackPtr, + \sprintf(self::METRIC_NAME, \ucfirst($phrase)), + ($isComma === true ? 'yes' : 'no') + ); + + switch ($action) { + case 'enforce': + if ($isComma === true) { + return; + } + + $error = 'There should be a comma after the last array item in a %s array.'; + $errorCode = 'Missing' . $errorCode; + $data = [$phrase]; + $fix = $phpcsFile->addFixableError($error, $lastNonEmpty, $errorCode, $data); + if ($fix === true) { + $extraContent = ','; + + if (($tokens[$lastNonEmpty]['code'] === \T_END_HEREDOC + || $tokens[$lastNonEmpty]['code'] === \T_END_NOWDOC) + // Check for indentation, if indented, it's a PHP 7.3+ heredoc/nowdoc. + && $tokens[$lastNonEmpty]['content'] === \ltrim($tokens[$lastNonEmpty]['content']) + ) { + // Prevent parse errors in PHP < 7.3 which doesn't support flexible heredoc/nowdoc. + $extraContent = $phpcsFile->eolChar . $extraContent; + } + + $phpcsFile->fixer->addContent($lastNonEmpty, $extraContent); + } + + return; + + case 'forbid': + if ($isComma === false) { + return; + } + + $error = 'A comma after the last array item in a %s array is not allowed.'; + $errorCode = 'Found' . $errorCode; + $data = [$phrase]; + $fix = $phpcsFile->addFixableError($error, $lastNonEmpty, $errorCode, $data); + if ($fix === true) { + $start = $lastNonEmpty; + $end = $lastNonEmpty; + + // Make sure we're not leaving a superfluous blank line behind. + $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($lastNonEmpty - 1), $opener, true); + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($lastNonEmpty + 1), ($closer + 1), true); + if ($prevNonWhitespace !== false + && $tokens[$prevNonWhitespace]['line'] < $tokens[$lastNonEmpty]['line'] + && $nextNonWhitespace !== false + && $tokens[$nextNonWhitespace]['line'] > $tokens[$lastNonEmpty]['line'] + ) { + $start = ($prevNonWhitespace + 1); + } + + $phpcsFile->fixer->beginChangeset(); + + for ($i = $start; $i <= $end; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + return; + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/NormalizedArrays/ruleset.xml b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/ruleset.xml new file mode 100644 index 00000000..ce35adb5 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/NormalizedArrays/ruleset.xml @@ -0,0 +1,5 @@ + + + + A ruleset for PHP_CodeSniffer to check arrays for normalized format. + diff --git a/vendor/phpcsstandards/phpcsextra/README.md b/vendor/phpcsstandards/phpcsextra/README.md new file mode 100644 index 00000000..22ba5382 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/README.md @@ -0,0 +1,553 @@ +PHPCSExtra +===================================================== + + + +* [Introduction](#introduction) +* [Minimum Requirements](#minimum-requirements) +* [Installation](#installation) + + [Composer Project-based Installation](#composer-project-based-installation) + + [Composer Global Installation](#composer-global-installation) + + [Updating to a newer version](#updating-to-a-newer-version) +* [Features](#features) +* [Sniffs](#sniffs) + + [Modernize](#modernize) + + [NormalizedArrays](#normalizedarrays) + + [Universal](#universal) +* [Contributing](#contributing) +* [License](#license) + + +Introduction +------------------------------------------- + +PHPCSExtra is a collection of sniffs and standards for use with [PHP_CodeSniffer][phpcs-gh]. + + +Minimum Requirements +------------------------------------------- + +* PHP 5.4 or higher. +* [PHP_CodeSniffer][phpcs-gh] version **3.7.1** or higher. +* [PHPCSUtils][phpcsutils-gh] version **1.0.8** or higher. + + +Installation +------------------------------------------- + +Installing via Composer is highly recommended. + +[Composer](http://getcomposer.org/) will automatically install the project dependencies and register the rulesets from PHPCSExtra and other external standards with PHP_CodeSniffer using the [Composer PHPCS plugin][composer-installer-gh]. + +### Composer Project-based Installation + +Run the following from the root of your project: +```bash +composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true +composer require --dev phpcsstandards/phpcsextra:"^1.1.0" +``` + +### Composer Global Installation + +Alternatively, you may want to install this standard globally: +```bash +composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true +composer global require --dev phpcsstandards/phpcsextra:"^1.1.0" +``` + +### Updating to a newer version + +If you installed PHPCSExtra using either of the above commands, you can update to a newer version as follows: +```bash +# Project local install +composer update phpcsstandards/phpcsextra --with-dependencies + +# Global install +composer global update phpcsstandards/phpcsextra --with-dependencies +``` + +> If your project includes `require[-dev]`s for the `squizlabs/php_codesniffer`, `phpcsstandards/phpcsutils` or +> `dealerdirect/phpcodesniffer-composer-installer` packages in its `composer.json` file, you may need to use +> `--with-all-dependencies` instead of `--with-dependencies`. +> +> :bulb: **Pro-tip**: Unless your project is a PHPCS standard which actually uses any of these packages directly, +> it is recommended to remove these packages from your own `composer.json` file, in favour of letting PHPCSExtra +> (and potential other external PHPCS standards you use), manage the version requirements for these packages. + + +Features +------------------------------------------- + +Once this project is installed, you will see three new rulesets in the list of installed standards when you run `vendor/bin/phpcs -i`: `Modernize`, `NormalizedArrays` and `Universal`. + +* The `Modernize` ruleset is a standard which checks code for modernization opportunaties. +* The `NormalizedArrays` ruleset is a standard to check the formatting of array declarations. +* The `Universal` ruleset is **NOT** a standard, but a sniff collection. + It should **NOT** be included in custom rulesets as a standard as it contains contradictory rules. + Instead include individual sniffs from this standard in a custom project/company ruleset to use them. + + +Sniffs +------------------------------------------- + +**Legend**: +* :wrench: = Includes auto-fixer. + _Use the `phpcbf` command to run the fixers._ +* :bar_chart: = Includes metrics. + _Use `phpcs` with `--report=info` to see the metrics._ +* :books: = Includes CLI documentation. + _Use `phpcs` with `--generator=Text` to see the documentation._ + + +### Modernize + +#### `Modernize.FunctionCalls.Dirname` :wrench: :books: + +This sniff will detect and auto-fix two typical code modernizations which can be made related to the `dirname()` function: +1. Since PHP 5.3, calls to `dirname(__FILE__)` can be replaced by `__DIR__`. + Errorcode: `Modernize.FunctionCalls.Dirname.FileConstant`. +2. Since PHP 7.0, nested function calls to `dirname()` can be changed to use the `$levels` parameter. + Errorcode: `Modernize.FunctionCalls.Dirname.Nested`. + +If a [`php_version` configuration option][php_version-config] has been passed to PHPCS using either `--config-set` or `--runtime-set`, it will be respected by the sniff. +In effect, this means that the sniff will only report on modernizations which can be applied for the PHP version as configured. + + +### NormalizedArrays + +#### `NormalizedArrays.Arrays.ArrayBraceSpacing` :wrench: :bar_chart: :books: + +Enforce consistent spacing for the open/close braces of array declarations. + +The sniff allows for having different settings for: +- Space between the array keyword and the open parenthesis for long arrays via the `keywordSpacing` property. + Accepted values: (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. +- Spaces on the inside of the braces for empty arrays via the `spacesWhenEmpty` property. + Accepted values: (string) `newline`, (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. +- Spaces on the inside of the braces for single-line arrays via the `spacesSingleLine` property; + Accepted values: (int) number of spaces or `false` to turn this check off. Defaults to `0` spaces. +- Spaces on the inside of the braces for multi-line arrays via the `spacesMultiLine` property. + Accepted values: (string) `newline`, (int) number of spaces or `false` to turn this check off. Defaults to `newline`. + +Note: if any of the above properties are set to `newline`, it is recommended to also include an array indentation sniff. This sniff will not handle the indentation. + +#### `NormalizedArrays.Arrays.CommaAfterLast` :wrench: :bar_chart: :books: + +Enforce/forbid a comma after the last item in an array declaration. + +By default, this sniff will: +* Forbid a comma after the last array item for single-line arrays. +* Enforce a comma after the last array item for multi-line arrays. + +This can be changed for each type or array individually by setting the `singleLine` and/or `multiLine` properties in a custom ruleset. + +Use any of the following values to change the properties: `enforce`, `forbid` or `skip` to not check the comma after the last array item for a particular type of array. + +The default for the `singleLine` property is `forbid`. The default for the `multiLine` property is `enforce`. + + +### Universal + +#### `Universal.Arrays.DisallowShortArraySyntax` :wrench: :bar_chart: :books: + +Disallow short array syntax. + +In contrast to the PHPCS native `Generic.Arrays.DisallowShortArraySyntax` sniff, this sniff will ignore short list syntax and not cause parse errors when the fixer is used. + +#### `Universal.Arrays.DuplicateArrayKey` :books: + +Detects duplicate array keys in array declarations. + +The sniff will make a distinction between keys which will be duplicate in all PHP version and (numeric) keys which will only be a duplicate key in [PHP < 8.0 or PHP >= 8.0][php-rfc-negative_array_index]. + +If a [`php_version` configuration option][php_version-config] has been passed to PHPCS using either `--config-set` or `--runtime-set`, it will be respected by the sniff and only report duplicate keys for the configured PHP version. + +[php-rfc-negative_array_index]: https://wiki.php.net/rfc/negative_array_index + +#### `Universal.Arrays.MixedArrayKeyTypes` :books: + +Best practice sniff: don't use a mix of integer and string keys for array items. + +#### `Universal.Arrays.MixedKeyedUnkeyedArray` :books: + +Best practice sniff: don't use a mix of keyed and unkeyed array items. + +#### `Universal.Classes.DisallowAnonClassParentheses` :wrench: :bar_chart: :books: + +Disallow the use of parentheses when declaring an anonymous class without passing parameters. + +#### `Universal.Classes.RequireAnonClassParentheses` :wrench: :bar_chart: :books: + +Require the use of parentheses when declaring an anonymous class, whether parameters are passed or not. + +#### `Universal.Classes.DisallowFinalClass` :wrench: :bar_chart: :books: + +Disallow classes being declared `final`. + +#### `Universal.Classes.RequireFinalClass` :wrench: :bar_chart: :books: + +Require all non-`abstract` classes to be declared `final`. + +:warning: **Warning**: the auto-fixer for this sniff _may_ have unintended side-effects for applications and should be used with care! +This is considered a **_risky_ fixer**. + +#### `Universal.Classes.ModifierKeywordOrder` :wrench: :bar_chart: :books: + +Require a consistent modifier keyword order for class declarations. + +* This sniff contains an `order` property to specify the preferred order. + Accepted values: (string) `'extendability readonly'`|`'readonly extendability'`. Defaults to `'extendability readonly'`. + +#### `Universal.CodeAnalysis.ConstructorDestructorReturn` :wrench: :books: + +* Disallows return type declarations on constructor/destructor methods - error code: `ReturnTypeFound`, auto-fixable. +* Discourages constructor/destructor methods returning a value - error code: `ReturnValueFound`. + +If a [`php_version` configuration option][php_version-config] has been passed to PHPCS using either `--config-set` or `--runtime-set`, it will be respected by the sniff. +In effect, this means that the sniff will only report on PHP4-style constructors if the configured PHP version is less than 8.0. + +#### `Universal.CodeAnalysis.ForeachUniqueAssignment` :wrench: :books: + +Detects `foreach` control structures which use the same variable for both the key as well as the value assignment as this will lead to unexpected - and most likely unintended - behaviour. + +Note: The fixer will maintain the existing behaviour of the code. This may not be the _intended_ behaviour. + +#### `Universal.CodeAnalysis.NoEchoSprintf` :wrench: :books: + +Detects use of the inefficient `echo [v]sprintf(...);` combi. Use `[v]printf()` instead. + +#### `Universal.CodeAnalysis.StaticInFinalClass` :wrench: :books: + +Detects using `static` instead of `self` in OO constructs which are `final`. + +* The sniff has modular error codes to allow for making exceptions based on the type of use for `static`. + The available error codes are: `ReturnType`, `InstanceOf`, `NewInstance`, `ScopeResolution`. + +#### `Universal.Constants.LowercaseClassResolutionKeyword` :wrench: :bar_chart: :books: + +Enforce that the `class` keyword when used for class name resolution, i.e. `::class`, is in lowercase. + +#### `Universal.Constants.ModifierKeywordOrder` :wrench: :bar_chart: :books: + +Require a consistent modifier keyword order for OO constant declarations. + +* This sniff contains an `order` property to specify the preferred order. + Accepted values: (string) `'final visibility'`|`'visibility final'`. Defaults to `'final visibility'`. + +#### `Universal.Constants.UppercaseMagicConstants` :wrench: :bar_chart: :books: + +Enforce uppercase when using PHP native magic constants, like `__FILE__` et al. + +#### `Universal.ControlStructures.DisallowAlternativeSyntax` :wrench: :bar_chart: :books: + +Disallow using the alternative syntax for control structures. + +* This sniff contains an `allowWithInlineHTML` property to allow alternative syntax when inline HTML is used within the control structure. In all other cases, the use of the alternative syntax will still be disallowed. + Accepted values: (bool) `true`|`false`. Defaults to `false`. +* The sniff has modular error codes to allow for making exceptions based on specific control structures and/or specific control structures in combination with inline HTML. + The error codes follow the following pattern: `Found[ControlStructure][WithInlineHTML]`. Examples: `FoundIf`, `FoundSwitchWithInlineHTML`. + +#### `Universal.ControlStructures.DisallowLonelyIf` :wrench: :books: + +Disallow `if` statements as the only statement in an `else` block. + +Note: This sniff will not fix the indentation of the "inner" code. +It is strongly recommended to run this sniff together with the `Generic.WhiteSpace.ScopeIndent` sniff to get the correct indentation. + +#### `Universal.ControlStructures.IfElseDeclaration` :wrench: :bar_chart: :books: + +Verify that else(if) statements with braces are on a new line. + +#### `Universal.Files.SeparateFunctionsFromOO` :bar_chart: :books: + +Enforce for a file to either declare (global/namespaced) functions or declare OO structures, but not both. + +* Nested function declarations, i.e. functions declared within a function/method will be disregarded for the purposes of this sniff. + The same goes for anonymous classes, closures and arrow functions. +* Note: This sniff has no opinion on side effects. If you want to sniff for those, use the PHPCS native `PSR1.Files.SideEffects` sniff. +* Also note: This sniff has no opinion on multiple OO structures being declared in one file. + If you want to sniff for that, use the PHPCS native `Generic.Files.OneObjectStructurePerFile` sniff. + +#### `Universal.FunctionDeclarations.NoLongClosures` :bar_chart: :books: + +Detects "long" closures and recommends using a named function instead. + +The sniff is configurable by setting any of the following properties in a custom ruleset: +* `recommendedLines` (int): determines when a warning will be thrown. + Defaults to `5`, meaning a warning with the errorcode `ExceedsRecommended` will be thrown if the closure is more than 5 lines long. +* `maxLines` (int): determines when an error will be thrown. + Defaults to `8`, meaning that an error with the errorcode `ExceedsMaximum` will be thrown if the closure is more than 8 lines long. +* `ignoreCommentLines` (bool): whether or not comment-only lines should be ignored for the lines count. + Defaults to `true`. +* `ignoreEmptyLines` (bool): whether or not blank lines should be ignored for the lines count. + Defaults to `true`. + +#### `Universal.FunctionDeclarations.RequireFinalMethodsInTraits` :wrench: :bar_chart: :books: + +Enforce non-private, non-abstract methods in traits to be declared as `final`. + +The available error codes are: `NonFinalMethodFound` and `NonFinalMagicMethodFound`. + +#### `Universal.Lists.DisallowLongListSyntax` :wrench: :books: + +Disallow the use of long `list`s. + +> For metrics about the use of long lists vs short lists, please use the `Universal.Lists.DisallowShortListSyntax` sniff. + +#### `Universal.Lists.DisallowShortListSyntax` :wrench: :bar_chart: :books: + +Disallow the use of short lists. + +#### `Universal.Namespaces.DisallowDeclarationWithoutName` :bar_chart: :books: + +Disallow namespace declarations without a namespace name. + +This sniff only applies to namespace declarations using the curly brace syntax. + +#### `Universal.Namespaces.DisallowCurlyBraceSyntax` :bar_chart: :books: + +Disallow the use of the alternative namespace declaration syntax using curly braces. + +#### `Universal.Namespaces.EnforceCurlyBraceSyntax` :bar_chart: :books: + +Enforce the use of the alternative namespace syntax using curly braces. + +#### `Universal.Namespaces.OneDeclarationPerFile` :books: + +Disallow the use of multiple namespaces within a file. + +#### `Universal.NamingConventions.NoReservedKeywordParameterNames` :books: + +Disallow function parameters using reserved keywords as names, as this can quickly become confusing when people use them in function calls using named parameters + +* The sniff has modular error codes to allow for making exceptions for specific keywords. + The error codes follow the following pattern: `[keyword]Found`. + +#### `Universal.OOStructures.AlphabeticExtendsImplements` :wrench: :bar_chart: :books: + +Enforce that the names used in a class/enum "implements" statement or an interface "extends" statement are listed in alphabetic order. + +* This sniff contains a `orderby` property to determine the sort order to use for the statement. + If all names used are unqualified, the sort order won't make a difference. + However, if one or more of the names are partially or fully qualified, the chosen sort order will determine how the sorting between unqualified, partially and fully qualified names is handled. + The sniff supports two sort order options: + - _'name'_ : sort by the interface name only (default); + - _'full'_ : sort by the full name as used in the statement (without leading backslash). + In both cases, the sorting will be done using natural sort, case-insensitive. +* The sniff has modular error codes to allow for selective inclusion/exclusion: + - `ImplementsWrongOrder` - for "class implements" statements. + - `ImplementsWrongOrderWithComments` - for "class implements" statements interlaced with comments. These will not be auto-fixed. + - `ExtendsWrongOrder` - for "interface extends" statements. + - `ExtendsWrongOrderWithComments` - for "interface extends" statements interlaced with comments. These will not be auto-fixed. +* When fixing, the existing spacing between the names in an `implements`/`extends` statement will not be maintained. + The fixer will separate each name with a comma and one space. + If alternative formatting is desired, a sniff which will check and fix the formatting should be added to the ruleset. + +#### `Universal.Operators.DisallowLogicalAndOr` :bar_chart: :books: + +Enforce the use of the boolean `&&` and `||` operators instead of the logical `and`/`or` operators. + +:information_source: Note: as the [operator precedence](https://www.php.net/language.operators.precedence) of the logical operators is significantly lower than the operator precedence of boolean operators, this sniff does not contain an auto-fixer. + +#### `Universal.Operators.DisallowShortTernary` :bar_chart: :books: + +Disallow the use of short ternaries `?:`. + +While short ternaries are useful when used correctly, the principle of them is often misunderstood and they are more often than not used incorrectly, leading to hard to debug issues and/or PHP warnings/notices. + +#### `Universal.Operators.DisallowStandalonePostIncrementDecrement` :wrench: :bar_chart: :books: + +* Disallow the use of post-in/decrements in stand-alone statements - error codes: `PostDecrementFound` and `PostIncrementFound`. + Using pre-in/decrement is more in line with the principle of least astonishment and prevents bugs when code gets moved around at a later point in time. +* Discourages the use of multiple increment/decrement operators in a stand-alone statement - error code: `MultipleOperatorsFound`. + +#### `Universal.Operators.StrictComparisons` :wrench: :bar_chart: :books: + +Enforce the use of strict comparisons. + +:warning: **Warning**: the auto-fixer for this sniff _may_ cause bugs in applications and should be used with care! +This is considered a **_risky_ fixer**. + +#### `Universal.Operators.TypeSeparatorSpacing` :wrench: :bar_chart: :books: + +Enforce no spaces around the union type and intersection type operators. + +The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`. + +#### `Universal.PHP.OneStatementInShortEchoTag` :wrench: :books: + +Disallow short open echo tags ` _This sniff is especially useful for tab-indentation based standards which use the `Generic.Whitespace.DisallowSpaceIndent` sniff to enforce this._ +> +> **DO** make sure to set the PHPCS native `tab-width` configuration for the best results. +> ```xml +> +> ``` +> +> The PHPCS native `Generic.Whitespace.DisallowTabIndent` sniff (used for space-based standards) oversteps its reach and silently does mid-line tab to space replacements as well. +> However, the sister-sniff `Generic.Whitespace.DisallowSpaceIndent` leaves mid-line tabs/spaces alone. +> This sniff fills that gap. + +#### `Universal.WhiteSpace.PrecisionAlignment` :wrench: :books: + +Enforce code indentation to always be a multiple of a tabstop, i.e. disallow precision alignment. + +Note: +* This sniff does not concern itself with tabs versus spaces. + It is recommended to use the sniff in combination with the PHPCS native `Generic.WhiteSpace.DisallowTabIndent` or the `Generic.WhiteSpace.DisallowSpaceIndent` sniff. +* When using this sniff with tab-based standards, please ensure that the `tab-width` is set and either don't set the `$indent` property or set it to the tab-width (or a multiple thereof). +* The fixer works based on "best guess" and may not always result in the desired indentation. Combine this sniff with the `Generic.WhiteSpace.ScopeIndent` sniff for more precise indentation fixes. + +The behaviour of the sniff is customizable via the following properties: +* `indent`: the indent used for the codebase. + Accepted values: (int|null) number of spaces. Defaults to `null`. + If this property is not set, the sniff will look to the `--tab-width` CLI value. + If that also isn't set, the default tab-width of `4` will be used. +* `ignoreAlignmentBefore`: allows for providing a list of token names for which (preceding) precision alignment should be ignored. + Accepted values: (array) token constant names. Defaults to an empty array. + Usage example: + ```xml + + + + + + + + + + + + ``` +* `ignoreBlankLines`: whether or not potential trailing whitespace on otherwise blank lines should be examined or ignored. + It is recommended to only set this to `false` if the standard including this sniff does not include the `Squiz.WhiteSpace.SuperfluousWhitespace` sniff (which is included in most standards). + Accepted values: (bool)`true`|`false`. Defaults to `true`. + + +Contributing +------- +Contributions to this project are welcome. Clone the repo, branch off from `develop`, make your changes, commit them and send in a pull request. + +If unsure whether the changes you are proposing would be welcome, open an issue first to discuss your proposal. + +License +------- +This code is released under the [GNU Lesser General Public License (LGPLv3)](LICENSE). + + +[phpcsextra-packagist]: https://packagist.org/packages/phpcsstandards/phpcsextra +[gha-qa-results]: https://github.com/PHPCSStandards/PHPCSExtra/actions/workflows/basics.yml +[gha-test-results]: https://github.com/PHPCSStandards/PHPCSExtra/actions/workflows/test.yml + +[phpcs-gh]: https://github.com/squizlabs/PHP_CodeSniffer +[phpcsutils-gh]: https://github.com/PHPCSStandards/PHPCSUtils +[composer-installer-gh]: https://github.com/PHPCSStandards/composer-installer + +[php_version-config]: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-php-version diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DisallowShortArraySyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DisallowShortArraySyntaxStandard.xml new file mode 100644 index 00000000..4a84b536 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DisallowShortArraySyntaxStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + array( + 'foo' => 'bar', +); + ]]> + + + [ + 'foo' => 'bar', +]; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml new file mode 100644 index 00000000..00f9b7bb --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml @@ -0,0 +1,44 @@ + + + + + + + + 'foo' => 22, + 'bar' => 25, + 'baz' => 28, +); + +$args = array( + 22, + 25, + 2 => 28, +); + ]]> + + + 'foo' => 22, + 'bar' => 25, + 'bar' => 28, +); + +$args = array( + 22, + 25, + 1 => 28, +); + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml new file mode 100644 index 00000000..f7efdda1 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml @@ -0,0 +1,40 @@ + + + + + + + + 'foo' => 22, + 'bar' => 25, +); + +$args = array( + 0 => 22, + 1 => 25, +); + ]]> + + + 22, + 25, +); + +$args = array( + 'foo' => 22, + 12 => 25, +); + + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml new file mode 100644 index 00000000..79897c19 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml @@ -0,0 +1,31 @@ + + + + + + + + 'foo' => 22, + 'bar' => 25, +); + +$args = array(22, 25); + ]]> + + + 22, + 25, +); + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml new file mode 100644 index 00000000..ebfd7c6c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml @@ -0,0 +1,24 @@ + + + + + + + + {}; +$anon = new class($param) {}; + ]]> + + + () {}; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowFinalClassStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowFinalClassStandard.xml new file mode 100644 index 00000000..fd7ddb67 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/DisallowFinalClassStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + class Foo {} +abstract class Bar implements MyInterface {} + ]]> + + + final class Foo {} +final class Bar extends MyAbstract {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/ModifierKeywordOrderStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/ModifierKeywordOrderStandard.xml new file mode 100644 index 00000000..a80ae0da --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/ModifierKeywordOrderStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + final readonly class Foo {} +abstract readonly class Bar {} + ]]> + + + readonly final class Foo {} +readonly abstract class Bar {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml new file mode 100644 index 00000000..48fa148e --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + () {}; + ]]> + + + {}; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireFinalClassStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireFinalClassStandard.xml new file mode 100644 index 00000000..107b8f41 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Classes/RequireFinalClassStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + final class Foo {} +final class Bar extends MyAbstract {} + ]]> + + + class Foo {} +abstract class Bar implements MyInterface {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml new file mode 100644 index 00000000..b44b4777 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + : int {} +} + ]]> + + + + + + + + + + + + $this; + } + + public function __destruct() { + // Do something. + return false; + } +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ForeachUniqueAssignmentStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ForeachUniqueAssignmentStandard.xml new file mode 100644 index 00000000..6f9676ee --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/ForeachUniqueAssignmentStandard.xml @@ -0,0 +1,26 @@ + + + + + + + + $v ) {} + ]]> + + + $k ) {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml new file mode 100644 index 00000000..914eb65d --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + printf('text %s text', $var); +echo callMe('text %s text', $var); + ]]> + + + echo sprintf('text %s text', $var); +echo vsprintf('text %s text', [$var]); + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/StaticInFinalClassStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/StaticInFinalClassStandard.xml new file mode 100644 index 00000000..5f9f45bd --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/CodeAnalysis/StaticInFinalClassStandard.xml @@ -0,0 +1,43 @@ + + + + + + + + self + { + $var = self::functionCall(); + $var = $obj instanceof self; + $var = new self; + } +} + ]]> + + + static|false { + $var = static::$prop; + $var = $obj instanceof static; + $var = new static(); + } +}; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml new file mode 100644 index 00000000..7b61c8fa --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + MyClass::CLASS; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/ModifierKeywordOrderStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/ModifierKeywordOrderStandard.xml new file mode 100644 index 00000000..f42483a0 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/ModifierKeywordOrderStandard.xml @@ -0,0 +1,30 @@ + + + + + + + + final public const FOO = 'foo'; +} + ]]> + + + protected final const BAR = 'foo'; +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml new file mode 100644 index 00000000..2a9583fa --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + __LINE__; +include __DIR__ . '/file.php'; + ]]> + + + __NameSpace__; +include dirname(__file__) . '/file.php'; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml new file mode 100644 index 00000000..4be7e6df --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml @@ -0,0 +1,35 @@ + + + + + + + + { + $var = 1; +} + +while (++$i < 10) { + echo $i; +} + ]]> + + + : + $var = 1; +endif; + +while (++$i < 10): + echo $i; +endwhile; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml new file mode 100644 index 00000000..88d09228 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml @@ -0,0 +1,49 @@ + + + + + + + + elseif ($bar) { + // ... +} + +if ($foo) { + // ... +} else { + if ($bar) { + // ... + } + + doSomethingElse(); + +} + + ]]> + + + if ($bar) { + // ... + } else { + // ... + } +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml new file mode 100644 index 00000000..b1506f64 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml @@ -0,0 +1,37 @@ + + + + + + + + +elseif ($bar) { + $var = 2; +} +else { + $var = 3; +} + ]]> + + + elseif ($bar) { + $var = 2; +} else { + $var = 3; +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Files/SeparateFunctionsFromOOStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Files/SeparateFunctionsFromOOStandard.xml new file mode 100644 index 00000000..db0a2671 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Files/SeparateFunctionsFromOOStandard.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/NoLongClosuresStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/NoLongClosuresStandard.xml new file mode 100644 index 00000000..dc84e0b1 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/NoLongClosuresStandard.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/RequireFinalMethodsInTraitsStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/RequireFinalMethodsInTraitsStandard.xml new file mode 100644 index 00000000..4b2622de --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/FunctionDeclarations/RequireFinalMethodsInTraitsStandard.xml @@ -0,0 +1,33 @@ + + + + + + + + final public function bar() {} + final public static function baz() {} + + // Also valid (out of scope): + protected abstract function overload() {} + private function okay() {} +} + ]]> + + + public function bar() {} + protected static function baz() {} +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml new file mode 100644 index 00000000..35475eab --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + [$a, $b] = $array; + ]]> + + + list($a, $b) = $array; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml new file mode 100644 index 00000000..214b6862 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + list($a, $b) = $array; + ]]> + + + [$a, $b] = $array; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml new file mode 100644 index 00000000..95174d73 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + ; + +// Code + ]]> + + + { + // Code. +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowDeclarationWithoutNameStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowDeclarationWithoutNameStandard.xml new file mode 100644 index 00000000..465ea556 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/DisallowDeclarationWithoutNameStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + Vendor\Name { +} + ]]> + + + { +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml new file mode 100644 index 00000000..8e1892ca --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + { + // Code. +} + ]]> + + + ; + +// Code + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml new file mode 100644 index 00000000..6aa0f15c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + namespace Vendor\Project\Sub\B { +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/NamingConventions/NoReservedKeywordParameterNamesStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/NamingConventions/NoReservedKeywordParameterNamesStandard.xml new file mode 100644 index 00000000..d44935cf --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/NamingConventions/NoReservedKeywordParameterNamesStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/OOStructures/AlphabeticExtendsImplementsStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/OOStructures/AlphabeticExtendsImplementsStandard.xml new file mode 100644 index 00000000..f22a7193 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/OOStructures/AlphabeticExtendsImplementsStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + Bar, Foo +{ +} + ]]> + + + Foo, Bar +{ +} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowLogicalAndOrStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowLogicalAndOrStandard.xml new file mode 100644 index 00000000..4e398784 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowLogicalAndOrStandard.xml @@ -0,0 +1,30 @@ + + + + + + + + && $var > 10) {} + +if (empty($var) || $var < 0) {} + ]]> + + + and $var > 10) {} + +if (empty($var) or $var < 0) {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowShortTernaryStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowShortTernaryStandard.xml new file mode 100644 index 00000000..0ef2ea31 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowShortTernaryStandard.xml @@ -0,0 +1,26 @@ + + + + + + + + ? $a : 'default'; + ]]> + + + ?: 'default'; +echo $a ? : 'default'; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowStandalonePostIncrementDecrementStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowStandalonePostIncrementDecrementStandard.xml new file mode 100644 index 00000000..06780034 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/DisallowStandalonePostIncrementDecrementStandard.xml @@ -0,0 +1,44 @@ + + + + + + + + ++$i; +--$j; + ]]> + + + ++; +$j--; + ]]> + + + + + + + + ++$i; + ]]> + + + --$i++++; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/StrictComparisonsStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/StrictComparisonsStandard.xml new file mode 100644 index 00000000..b4507ede --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/StrictComparisonsStandard.xml @@ -0,0 +1,29 @@ + + + + + + + + === 'text') {} + +if ($var !== true) {} + ]]> + + + == 'text') {} + +if ($var != true) {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml new file mode 100644 index 00000000..3d3c328e --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml @@ -0,0 +1,33 @@ + + + + + + + + |string $paramA, + TypeA&TypeB $paramB +): int|false {} + ]]> + + + | string $paramA, + TypeA & TypeB $paramB +): int + | + false {} + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/PHP/OneStatementInShortEchoTagStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/PHP/OneStatementInShortEchoTagStandard.xml new file mode 100644 index 00000000..103ca13e --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/PHP/OneStatementInShortEchoTagStandard.xml @@ -0,0 +1,41 @@ + + + + + + + + + ]]> + + + $text; echo $moreText; ?> + ]]> + + + + + +echo $text; +echo $moreText; +?> + ]]> + + + $text; +echo $moreText; +?> + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowMixedGroupUseStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowMixedGroupUseStandard.xml new file mode 100644 index 00000000..2b930f27 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowMixedGroupUseStandard.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseClassStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseClassStandard.xml new file mode 100644 index 00000000..1699d9db --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseClassStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseConstStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseConstStandard.xml new file mode 100644 index 00000000..0dbec70b --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseConstStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml new file mode 100644 index 00000000..f3cb1f67 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/KeywordSpacingStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/KeywordSpacingStandard.xml new file mode 100644 index 00000000..55d430e2 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/KeywordSpacingStandard.xml @@ -0,0 +1,29 @@ + + + + + + + + function strpos; +use const PHP_EOL as MY_EOL; + ]]> + + + function strpos; +use + const + PHP_EOL + as + MY_EOL; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/LowercaseFunctionConstStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/LowercaseFunctionConstStandard.xml new file mode 100644 index 00000000..66acd83b --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/LowercaseFunctionConstStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + function strpos; +use const PHP_EOL; + ]]> + + + Function strpos; +use CONST PHP_EOL; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoLeadingBackslashStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoLeadingBackslashStandard.xml new file mode 100644 index 00000000..ff0b786c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoLeadingBackslashStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + use Package\ClassName; + ]]> + + + \Package\ClassName; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoUselessAliasesStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoUselessAliasesStandard.xml new file mode 100644 index 00000000..39bb90d1 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/UseStatements/NoUselessAliasesStandard.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/AnonClassKeywordSpacingStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/AnonClassKeywordSpacingStandard.xml new file mode 100644 index 00000000..1770f747 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/AnonClassKeywordSpacingStandard.xml @@ -0,0 +1,31 @@ + + + + + + + + class($param) +{ + public function __construct($p) {} +}; + ]]> + + + class ($param) +{ + public function __construct($p) {} +}; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/CommaSpacingStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/CommaSpacingStandard.xml new file mode 100644 index 00000000..503ae6f7 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/CommaSpacingStandard.xml @@ -0,0 +1,94 @@ + + + + + + + + , $param2, $param3); + +function_call( + $param1, + $param2, + $param3 +); + +$array = array($item1, $item2, $item3); +$array = [ + $item1, + $item2, +]; + +list(, $a, $b,,) = $array; +list( + , + $a, + $b, +) = $array; + ]]> + + + , $param2,$param3); + +function_call( + $a + ,$b + ,$c +); + +$array = array($item1,$item2 , $item3); +$array = [ + $item1, + $item2 , +]; + +list( ,$a, $b ,,) = $array; +list( + , + $a, + $b , +) = $array; + ]]> + + + + + + + + , // Comment. + $param2, /* Comment. */ +); + ]]> + + + , + $param2 /* Comment. */, +); + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/DisallowInlineTabsStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/DisallowInlineTabsStandard.xml new file mode 100644 index 00000000..b259ab40 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/DisallowInlineTabsStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + [space]= 'text'; +$text[space][space]= 'more text'; + ]]> + + + [tab]= 'text'; +$text[tab]= 'more text'; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/PrecisionAlignmentStandard.xml b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/PrecisionAlignmentStandard.xml new file mode 100644 index 00000000..cde20f25 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Docs/WhiteSpace/PrecisionAlignmentStandard.xml @@ -0,0 +1,29 @@ + + + + + + + + [space][space][space][space]$foo = 'bar'; + +[tab]$foo = 'bar'; + ]]> + + + [space][space]$foo = 'bar'; + +[tab][space]$foo = 'bar'; + ]]> + + + diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Helpers/DummyTokenizer.php b/vendor/phpcsstandards/phpcsextra/Universal/Helpers/DummyTokenizer.php new file mode 100644 index 00000000..7c172e34 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Helpers/DummyTokenizer.php @@ -0,0 +1,60 @@ +eolChar = $eolChar; + $this->config = $config; + } + + /** + * Creates an array of tokens when given some content. + * + * @param string $string The string to tokenize. + * + * @return array> + */ + protected function tokenize($string) + { + return []; + } + + /** + * Performs additional processing after main tokenizing. + * + * @return void + */ + protected function processAdditional() + { + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php new file mode 100644 index 00000000..829daaa9 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php @@ -0,0 +1,89 @@ + + */ + public function register() + { + return Collections::arrayOpenTokensBC(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] === \T_ARRAY) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + return; + } + + if (Arrays::isShortArray($phpcsFile, $stackPtr) === false) { + // Square brackets, but not a short array. Probably short list or real square brackets. + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + + $error = 'Short array syntax is not allowed'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['bracket_opener'], 'array('); + $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['bracket_closer'], ')'); + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php new file mode 100644 index 00000000..7097fad7 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php @@ -0,0 +1,297 @@ +> + */ + private $keysSeenLt8 = []; + + /** + * Keep track of which array keys have been seen already on PHP >= 8.0. + * + * @since 1.0.0 + * + * @var array> + */ + private $keysSeenGt8 = []; + + /** + * Keep track of the maximum seen integer key to know what the next value will be for + * array items without a key on PHP < 8.0. + * + * @since 1.0.0 + * + * @var int + */ + private $currentMaxIntKeyLt8; + + /** + * Keep track of the maximum seen integer key to know what the next value will be for + * array items without a key on PHP >= 8.0. + * + * @since 1.0.0 + * + * @var int + */ + private $currentMaxIntKeyGt8; + + /** + * PHP version as configured or -1 if unknown. + * + * @since 1.0.0 + * + * @var int + */ + private $phpVersion; + + /** + * Process every part of the array declaration. + * + * This contains the default logic for the sniff, but can be overloaded in a concrete child class + * if needed. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * + * @return void + */ + public function processArray(File $phpcsFile) + { + // Reset properties before processing this array. + $this->keysSeenLt8 = []; + $this->keysSeenGt8 = []; + + if (isset($this->phpVersion) === false) { + // Set default value to prevent this code from running every time the sniff is triggered. + $this->phpVersion = -1; + + $phpVersion = Helper::getConfigData('php_version'); + if ($phpVersion !== null) { + $this->phpVersion = (int) $phpVersion; + } + } + + unset($this->currentMaxIntKeyLt8, $this->currentMaxIntKeyGt8); + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); + + if (isset($key) === false) { + // Key could not be determined. + return; + } + + $integerKey = \is_int($key); + + $errorMsg = 'Duplicate array key found. The value will be overwritten%s.' + . ' The %s array key "%s" was first seen on line %d'; + $errorCode = 'Found'; + $errors = []; + $baseData = [ + ($integerKey === true) ? 'integer' : 'string', + $key, + ]; + + /* + * Check if we've seen the key before. + */ + if (($this->phpVersion === -1 || $this->phpVersion < 80000) + && isset($this->keysSeenLt8[$key]) === true + ) { + $errors['phplt8'] = [ + 'data_subset' => $baseData, + 'error_suffix' => '', + 'code_suffix' => '', + ]; + + if ($integerKey === true) { + $errors['phplt8']['error_suffix'] = ' when using PHP < 8.0'; + $errors['phplt8']['code_suffix'] = 'ForPHPlt80'; + } + + $firstSeen = $this->keysSeenLt8[$key]; + $firstNonEmptyFirstSeen = $phpcsFile->findNext(Tokens::$emptyTokens, $firstSeen['ptr'], null, true); + + $errors['phplt8']['data_subset'][] = $this->tokens[$firstNonEmptyFirstSeen]['line']; + } + + if (($this->phpVersion === -1 || $this->phpVersion >= 80000) + && isset($this->keysSeenGt8[$key]) === true + ) { + $errors['phpgt8'] = [ + 'data_subset' => $baseData, + 'error_suffix' => '', + 'code_suffix' => '', + ]; + + if ($integerKey === true) { + $errors['phpgt8']['error_suffix'] = ' when using PHP >= 8.0'; + $errors['phpgt8']['code_suffix'] = 'ForPHPgte80'; + } + + $firstSeen = $this->keysSeenGt8[$key]; + $firstNonEmptyFirstSeen = $phpcsFile->findNext(Tokens::$emptyTokens, $firstSeen['ptr'], null, true); + + $errors['phpgt8']['data_subset'][] = $this->tokens[$firstNonEmptyFirstSeen]['line']; + } + + /* + * Throw the error(s). + * + * If no PHP version was passed, throw errors both for PHP < 8.0 and PHP >= 8.0. + * If a PHP version was set, only throw the error appropriate for the selected PHP version. + * If both errors would effectively be the same, only throw one. + */ + if ($errors !== []) { + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + + if (isset($errors['phplt8'], $errors['phpgt8']) + && $errors['phplt8']['data_subset'] === $errors['phpgt8']['data_subset'] + ) { + // Only throw the error once if it would be the same for PHP < 8.0 and PHP >= 8.0. + $data = $errors['phplt8']['data_subset']; + \array_unshift($data, ''); + + $phpcsFile->addError($errorMsg, $firstNonEmpty, $errorCode, $data); + return; + } + + if (isset($errors['phplt8'])) { + $code = $errorCode . $errors['phplt8']['code_suffix']; + $data = $errors['phplt8']['data_subset']; + \array_unshift($data, $errors['phplt8']['error_suffix']); + + $phpcsFile->addError($errorMsg, $firstNonEmpty, $code, $data); + } + + if (isset($errors['phpgt8'])) { + $code = $errorCode . $errors['phpgt8']['code_suffix']; + $data = $errors['phpgt8']['data_subset']; + \array_unshift($data, $errors['phpgt8']['error_suffix']); + + $phpcsFile->addError($errorMsg, $firstNonEmpty, $code, $data); + } + + return; + } + + /* + * Key not seen before. Add to arrays. + */ + $this->keysSeenLt8[$key] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + $this->keysSeenGt8[$key] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + + if ($integerKey === true) { + if ((isset($this->currentMaxIntKeyLt8) === false && $key > -1) + || (isset($this->currentMaxIntKeyLt8) === true && $key > $this->currentMaxIntKeyLt8) + ) { + $this->currentMaxIntKeyLt8 = $key; + } + + if (isset($this->currentMaxIntKeyGt8) === false + || $key > $this->currentMaxIntKeyGt8 + ) { + $this->currentMaxIntKeyGt8 = $key; + } + } + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + // Track the key for PHP < 8.0. + if (isset($this->currentMaxIntKeyLt8) === false) { + $this->currentMaxIntKeyLt8 = -1; + } + + ++$this->currentMaxIntKeyLt8; + $this->keysSeenLt8[$this->currentMaxIntKeyLt8] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + + // Track the key for PHP 8.0+. + if (isset($this->currentMaxIntKeyGt8) === false) { + $this->currentMaxIntKeyGt8 = -1; + } + + ++$this->currentMaxIntKeyGt8; + $this->keysSeenGt8[$this->currentMaxIntKeyGt8] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php new file mode 100644 index 00000000..49d9260b --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php @@ -0,0 +1,174 @@ +seenStringKey = false; + $this->seenNumericKey = false; + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing. + * In effect, this means that the sniff will not examine the double arrow, the array + * value or comma for this array item and will not process any array items after this one. + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); + if (isset($key) === false) { + // Key could not be determined. + return; + } + + $integerKey = \is_int($key); + + // Handle integer key. + if ($integerKey === true) { + if ($this->seenStringKey === false) { + if ($this->seenNumericKey !== false) { + // Already seen a numeric key before. + return; + } + + $this->seenNumericKey = true; + return; + } + + // Ok, so we've seen a string key before and now see an explicit numeric key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. Explicit numeric key detected,' + . ' while all previous keys in this array were string keys.', + $firstNonEmpty, + 'ExplicitNumericKey' + ); + + // Stop the loop. + return true; + } + + // Handle string key. + if ($this->seenNumericKey === false) { + if ($this->seenStringKey !== false) { + // Already seen a string key before. + return; + } + + $this->seenStringKey = true; + return; + } + + // Ok, so we've seen a numeric key before and now see a string key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. String key detected,' + . ' while all previous keys in this array were integer based keys.', + $firstNonEmpty, + 'StringKey' + ); + + // Stop the loop. + return true; + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing. + * In effect, this means that the sniff will not examine the array value or + * comma for this array item and will not process any array items after this one. + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + if ($this->seenStringKey === false) { + if ($this->seenNumericKey !== false) { + // Already seen a numeric key before. + return; + } + + $this->seenNumericKey = true; + return; + } + + // Ok, so we've seen a string key before and now see an implicit numeric key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. Implicit numeric key detected,' + . ' while all previous keys in this array were string keys.', + $firstNonEmpty, + 'ImplicitNumericKey' + ); + + // Stop the loop. + return true; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php new file mode 100644 index 00000000..c47f217f --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php @@ -0,0 +1,134 @@ + Key is the item number; value the stack point to the first non empty token in the item. + */ + private $itemsWithoutKey = []; + + /** + * Process the array declaration. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * + * @return void + */ + public function processArray(File $phpcsFile) + { + // Reset properties before processing this array. + $this->hasKeys = false; + $this->itemsWithoutKey = []; + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $this->hasKeys = true; + + // Process any previously encountered items without keys. + if (empty($this->itemsWithoutKey) === false) { + foreach ($this->itemsWithoutKey as $itemNr => $stackPtr) { + $phpcsFile->addError( + 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' + . ' The array item in position %d does not have an array key.', + $stackPtr, + 'Found', + [$itemNr] + ); + } + + // No need to do this again. + $this->itemsWithoutKey = []; + } + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + if ($firstNonEmpty === false || $this->tokens[$firstNonEmpty]['code'] === \T_COMMA) { + // Shouldn't really be possible, but this must be a parse error (empty array item). + return; + } + + // If we already know there are keys in the array, throw an error message straight away. + if ($this->hasKeys === true) { + $phpcsFile->addError( + 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' + . ' The array item in position %d does not have an array key.', + $firstNonEmpty, + 'Found', + [$itemNr] + ); + } else { + // Save the array item info for later in case we do encounter an array key later on in the array. + $this->itemsWithoutKey[$itemNr] = $firstNonEmpty; + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php new file mode 100644 index 00000000..dae01252 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php @@ -0,0 +1,112 @@ + + */ + public function register() + { + return [\T_ANON_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + // Note: no need to check for `false` as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. + if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) { + // No parentheses found. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + return; + } + + if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) { + /* + * Incomplete set of parentheses. Ignore. + * Shouldn't be possible as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. + */ + // @codeCoverageIgnoreStart + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + return; + // @codeCoverageIgnoreEnd + } + + $opener = $nextNonEmpty; + $closer = $tokens[$opener]['parenthesis_closer']; + $hasParams = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true); + if ($hasParams !== false) { + // There is something between the parentheses. Ignore. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes, with parameter(s)'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + + $fix = $phpcsFile->addFixableError( + 'Parenthesis not allowed when creating a new anonymous class without passing parameters', + $stackPtr, + 'Found' + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + for ($i = $opener; $i <= $closer; $i++) { + if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { + continue; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowFinalClassSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowFinalClassSniff.php new file mode 100644 index 00000000..4e3f4719 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/DisallowFinalClassSniff.php @@ -0,0 +1,116 @@ + + */ + public function register() + { + return [\T_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); + if ($classProp['is_final'] === false) { + if ($classProp['is_abstract'] === true) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false) { + // Live coding or parse error. + return; + } + + // No extra safeguards needed, we know the keyword will exist based on the check above. + $finalKeyword = $phpcsFile->findPrevious(\T_FINAL, ($stackPtr - 1)); + $snippetEnd = $nextNonEmpty; + $classCloser = ''; + + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $snippetEnd = $tokens[$stackPtr]['scope_opener']; + $classCloser = '}'; + } + + $snippet = GetTokensAsString::compact($phpcsFile, $finalKeyword, $snippetEnd, true); + $fix = $phpcsFile->addFixableError( + 'Declaring a class as final is not allowed. Found: %s%s', + $finalKeyword, + 'FinalClassFound', + [$snippet, $classCloser] + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($finalKeyword, ''); + + // Remove redundant whitespace. + for ($i = ($finalKeyword + 1); $i < $stackPtr; $i++) { + if ($tokens[$i]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + continue; + } + + break; + } + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/ModifierKeywordOrderSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/ModifierKeywordOrderSniff.php new file mode 100644 index 00000000..88fdfe20 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/ModifierKeywordOrderSniff.php @@ -0,0 +1,188 @@ + + */ + public function register() + { + return [\T_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); + + if ($classProp['readonly_token'] === false + || ($classProp['final_token'] === false && $classProp['abstract_token'] === false) + ) { + /* + * Either no modifier keywords found at all; or only one type of modifier + * keyword (abstract/final or readonly) declared, but not both. No ordering needed. + */ + return; + } + + if ($classProp['final_token'] !== false && $classProp['abstract_token'] !== false) { + // Parse error. Ignore. + return; + } + + $readonly = $classProp['readonly_token']; + + if ($classProp['final_token'] !== false) { + $extendability = $classProp['final_token']; + } else { + $extendability = $classProp['abstract_token']; + } + + if ($readonly < $extendability) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::READONLY_EXTEND); + } else { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::EXTEND_READONLY); + } + + $message = 'Class modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; + + switch ($this->order) { + case self::READONLY_EXTEND: + if ($readonly < $extendability) { + // Order is correct. Nothing to do. + return; + } + + $this->handleError($phpcsFile, $extendability, $readonly); + break; + + case self::EXTEND_READONLY: + default: + if ($extendability < $readonly) { + // Order is correct. Nothing to do. + return; + } + + $this->handleError($phpcsFile, $readonly, $extendability); + break; + } + } + + /** + * Throw the error and potentially fix it. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $firstKeyword The position of the first keyword found. + * @param int $secondKeyword The position of the second keyword token. + * + * @return void + */ + private function handleError(File $phpcsFile, $firstKeyword, $secondKeyword) + { + $tokens = $phpcsFile->getTokens(); + + $message = 'Class modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; + $data = [ + $tokens[$secondKeyword]['content'] . ' ' . $tokens[$firstKeyword]['content'], + $tokens[$firstKeyword]['content'] . ' ' . $tokens[$secondKeyword]['content'], + ]; + + $fix = $phpcsFile->addFixableError($message, $firstKeyword, 'Incorrect', $data); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($secondKeyword, ''); + + // Prevent leaving behind trailing whitespace. + $i = ($secondKeyword + 1); + while ($tokens[$i]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + ++$i; + } + + // Use the original token content as the case used for keywords is not the concern of this sniff. + $phpcsFile->fixer->addContentBefore($firstKeyword, $tokens[$secondKeyword]['content'] . ' '); + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php new file mode 100644 index 00000000..2715b392 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php @@ -0,0 +1,81 @@ + + */ + public function register() + { + return [\T_ANON_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + // Note: no need to check for `false` as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. + if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_PARENTHESIS) { + // Parentheses found. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + + $fix = $phpcsFile->addFixableError( + 'Parenthesis required when creating a new anonymous class.', + $stackPtr, + 'Missing' + ); + + if ($fix === true) { + $phpcsFile->fixer->addContent($stackPtr, '()'); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireFinalClassSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireFinalClassSniff.php new file mode 100644 index 00000000..843f1add --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Classes/RequireFinalClassSniff.php @@ -0,0 +1,102 @@ + + */ + public function register() + { + return [\T_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); + if ($classProp['is_final'] === true) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); + return; + } + + if ($classProp['is_abstract'] === true) { + // Abstract classes can't be final. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false) { + // Live coding or parse error. + return; + } + + $snippetEnd = $nextNonEmpty; + $classCloser = ''; + + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $snippetEnd = $tokens[$stackPtr]['scope_opener']; + $classCloser = '}'; + } + + $snippet = GetTokensAsString::compact($phpcsFile, $stackPtr, $snippetEnd, true); + $fix = $phpcsFile->addFixableError( + 'A non-abstract class should be declared as final. Found: %s%s', + $stackPtr, + 'NonFinalClassFound', + [$snippet, $classCloser] + ); + + if ($fix === true) { + $phpcsFile->fixer->addContentBefore($stackPtr, 'final '); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php new file mode 100644 index 00000000..4c577884 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php @@ -0,0 +1,199 @@ + + */ + public function register() + { + return [\T_FUNCTION]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (isset($this->phpVersion) === false || \defined('PHP_CODESNIFFER_IN_TESTS')) { + // Set default value to prevent this code from running every time the sniff is triggered. + $this->phpVersion = 0; + + $phpVersion = Helper::getConfigData('php_version'); + if ($phpVersion !== null) { + $this->phpVersion = (int) $phpVersion; + } + } + + $scopePtr = Scopes::validDirectScope($phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + if ($scopePtr === false) { + // Not an OO method. + return; + } + + $functionName = FunctionDeclarations::getName($phpcsFile, $stackPtr); + $functionNameLC = \strtolower($functionName); + + if ($functionNameLC === '__construct' || $functionNameLC === '__destruct') { + $functionType = \sprintf('A "%s()" magic method', $functionNameLC); + } else { + // If the PHP version is explicitly set to PHP 8.0 or higher, ignore PHP 4-style constructors. + if ($this->phpVersion >= 80000) { + return; + } + + // This may be a PHP 4-style constructor which should be handled. + $OOName = ObjectDeclarations::getName($phpcsFile, $scopePtr); + + if (empty($OOName) === true) { + // Anonymous class or parse error. The function can't be a PHP 4-style constructor. + return; + } + + if (NamingConventions::isEqual($functionName, $OOName) === false) { + // Class and function name not the same, so not a PHP 4-style constructor. + return; + } + + $functionType = 'A PHP 4-style constructor'; + } + + /* + * OK, so now we know for sure that this is a constructor/destructor method. + */ + + // Check for a return type. + $tokens = $phpcsFile->getTokens(); + $properties = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); + if ($properties['return_type'] !== '' && $properties['return_type_token'] !== false) { + $data = [ + $functionType, + $properties['return_type'], + ]; + + $fix = $phpcsFile->addFixableError( + '%s can not declare a return type. Found: %s', + $properties['return_type_token'], + 'ReturnTypeFound', + $data + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $parensCloser = $tokens[$stackPtr]['parenthesis_closer']; + for ($i = ($parensCloser + 1); $i <= $properties['return_type_end_token']; $i++) { + if (isset(Tokens::$commentTokens[$tokens[$i]['code']])) { + // Ignore comments and leave them be. + continue; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + } + + if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { + // Abstract/interface method, live coding or parse error. + return; + } + + // Check for a value being returned. + $current = $tokens[$stackPtr]['scope_opener']; + $end = $tokens[$stackPtr]['scope_closer']; + + // Not searching for arrow functions as those have an implicit return, so no + $search = Collections::functionDeclarationTokens(); + $search[\T_RETURN] = \T_RETURN; + + do { + $current = $phpcsFile->findNext($search, ($current + 1), $end); + if ($current === false) { + break; + } + + if (isset(Collections::functionDeclarationTokens()[$tokens[$current]['code']]) + && isset($tokens[$current]['scope_closer']) + ) { + // Skip over nested function/closure declarations. + $current = $tokens[$current]['scope_closer']; + continue; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $end, true); + if ($next === false + || $tokens[$next]['code'] === \T_SEMICOLON + || $tokens[$next]['code'] === \T_CLOSE_TAG + ) { + // Return statement without value. + continue; + } + + $endOfStatement = BCFile::findEndOfStatement($phpcsFile, $next); + + $data = [ + $functionType, + GetTokensAsString::compact($phpcsFile, $current, $endOfStatement, true), + ]; + + $phpcsFile->addWarning( + '%s can not return a value. Found: "%s"', + $current, + 'ReturnValueFound', + $data + ); + } while ($current < $end); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ForeachUniqueAssignmentSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ForeachUniqueAssignmentSniff.php new file mode 100644 index 00000000..8895c56c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/ForeachUniqueAssignmentSniff.php @@ -0,0 +1,153 @@ + + */ + public function register() + { + return [\T_FOREACH]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) { + // Parse error or live coding, not our concern. + return; + } + + $opener = $tokens[$stackPtr]['parenthesis_opener']; + $closer = $tokens[$stackPtr]['parenthesis_closer']; + + $asPtr = $phpcsFile->findNext(\T_AS, ($opener + 1), $closer); + if ($asPtr === false) { + // Parse error or live coding, not our concern. + return; + } + + // Real target. + $find = [\T_DOUBLE_ARROW]; + // Prevent matching on double arrows within a list assignment. + $find += Collections::listTokens(); + + $doubleArrowPtr = $phpcsFile->findNext($find, ($asPtr + 1), $closer); + if ($doubleArrowPtr === false + || $tokens[$doubleArrowPtr]['code'] !== \T_DOUBLE_ARROW + ) { + // No key assignment. + return; + } + + $isListAssignment = $phpcsFile->findNext(Tokens::$emptyTokens, ($doubleArrowPtr + 1), $closer, true); + if ($isListAssignment === false) { + // Parse error or live coding, not our concern. + } + + $keyAsString = \ltrim(GetTokensAsString::noEmpties($phpcsFile, ($asPtr + 1), ($doubleArrowPtr - 1)), '&'); + $valueAssignments = []; + if (isset(Collections::listTokens()[$tokens[$isListAssignment]['code']]) === false) { + // Single value assignment. + $valueAssignments[] = GetTokensAsString::noEmpties($phpcsFile, ($doubleArrowPtr + 1), ($closer - 1)); + } else { + // List assignment. + $assignments = Lists::getAssignments($phpcsFile, $isListAssignment); + foreach ($assignments as $listItem) { + if ($listItem['assignment'] === '') { + // Ignore empty list assignments. + continue; + } + + // Note: this doesn't take nested lists into account (yet). + $valueAssignments[] = $listItem['assignment']; + } + } + + if (empty($valueAssignments)) { + // No assignments found. + return; + } + + foreach ($valueAssignments as $valueAsString) { + $valueAsString = \ltrim($valueAsString, '&'); + + if ($keyAsString !== $valueAsString) { + // Key and value not the same. + continue; + } + + $error = 'The variables used for the key and the value in a foreach assignment should be unique.'; + $error .= 'Both the key and the value will currently be assigned to: "%s"'; + + $fix = $phpcsFile->addFixableError($error, $doubleArrowPtr, 'NotUnique', [$valueAsString]); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + // Remove the key. + for ($i = ($asPtr + 1); $i < ($doubleArrowPtr + 1); $i++) { + if ($tokens[$i]['code'] === \T_WHITESPACE + && isset(Tokens::$commentTokens[$tokens[($i + 1)]['code']]) + ) { + // Don't remove whitespace when followed directly by a comment. + continue; + } + + if (isset(Tokens::$commentTokens[$tokens[$i]['code']])) { + // Don't remove comments. + continue; + } + + // Remove everything else. + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + break; + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/NoEchoSprintfSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/NoEchoSprintfSniff.php new file mode 100644 index 00000000..67035f2a --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/NoEchoSprintfSniff.php @@ -0,0 +1,131 @@ + + */ + private $targetFunctions = [ + 'sprintf' => 'printf', + 'vsprintf' => 'vprintf', + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.1.0 + * + * @return array + */ + public function register() + { + return [\T_ECHO]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $skip = Tokens::$emptyTokens; + $skip[] = \T_NS_SEPARATOR; + + $next = $phpcsFile->findNext($skip, ($stackPtr + 1), null, true); + if ($next === false + || $tokens[$next]['code'] !== \T_STRING + || isset($this->targetFunctions[\strtolower($tokens[$next]['content'])]) === false + ) { + // Not our target. + return; + } + + $detectedFunction = \strtolower($tokens[$next]['content']); + + $openParens = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true); + if ($openParens === false + || $tokens[$openParens]['code'] !== \T_OPEN_PARENTHESIS + || isset($tokens[$openParens]['parenthesis_closer']) === false + ) { + // Live coding/parse error. + return; + } + + $closeParens = $tokens[$openParens]['parenthesis_closer']; + $afterFunctionCall = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParens + 1), null, true); + if ($afterFunctionCall === false + || ($tokens[$afterFunctionCall]['code'] !== \T_SEMICOLON + && $tokens[$afterFunctionCall]['code'] !== \T_CLOSE_TAG) + ) { + // Live coding/parse error or compound echo statement. + return; + } + + $fix = $phpcsFile->addFixableError( + 'Unnecessary "echo %s(...)" found. Use "%s(...)" instead.', + $next, + 'Found', + [ + $tokens[$next]['content'], + $this->targetFunctions[$detectedFunction], + ] + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + // Remove echo and whitespace. + $phpcsFile->fixer->replaceToken($stackPtr, ''); + + for ($i = ($stackPtr + 1); $i < $next; $i++) { + if ($tokens[$i]['code'] !== \T_WHITESPACE) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->replaceToken($next, $this->targetFunctions[$detectedFunction]); + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/StaticInFinalClassSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/StaticInFinalClassSniff.php new file mode 100644 index 00000000..2136bcdb --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/StaticInFinalClassSniff.php @@ -0,0 +1,216 @@ + + */ + private $validOOScopes = [ + \T_CLASS, // Only if final. + \T_ANON_CLASS, // Final by nature. + \T_ENUM, // Final by design. + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return [ + // These tokens are used to retrieve return types reliably. + \T_FUNCTION, + \T_FN, + // While this is our "real" target. + \T_STATIC, + // But we also need this as after "instanceof", `static` is tokenized as `T_STRING in PHPCS < 4.0.0. + // See: https://github.com/squizlabs/PHP_CodeSniffer/pull/3121 + \T_STRING, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] === \T_STRING + && \strtolower($tokens[$stackPtr]['content']) !== 'static' + ) { + return; + } + + if ($tokens[$stackPtr]['code'] === \T_FUNCTION + || $tokens[$stackPtr]['code'] === \T_FN + ) { + /* + * Check return types for methods in final classes, anon classes and enums. + * + * Will return the scope opener of the function to prevent potential duplicate notifications. + */ + $scopeOpener = $stackPtr; + if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $scopeOpener = $tokens[$stackPtr]['scope_opener']; + } + + if ($tokens[$stackPtr]['code'] === \T_FUNCTION) { + $ooPtr = Scopes::validDirectScope($phpcsFile, $stackPtr, $this->validOOScopes); + if ($ooPtr === false) { + // Method in a trait (not known where it is used), interface (never final) or not in an OO scope. + return $scopeOpener; + } + } else { + $ooPtr = Conditions::getLastCondition($phpcsFile, $stackPtr, $this->validOOScopes); + if ($ooPtr === false) { + // Arrow function outside of OO. + return $scopeOpener; + } + } + + if ($tokens[$ooPtr]['code'] === \T_CLASS) { + $classProps = ObjectDeclarations::getClassProperties($phpcsFile, $ooPtr); + if ($classProps['is_final'] === false) { + // Method in a non-final class. + return $scopeOpener; + } + } + + $functionProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); + if ($functionProps['return_type'] === '') { + return $scopeOpener; + } + + $staticPtr = $phpcsFile->findNext( + \T_STATIC, + $functionProps['return_type_token'], + ($functionProps['return_type_end_token'] + 1) + ); + + if ($staticPtr === false) { + return $scopeOpener; + } + + // Found a return type containing the `static` type. + $this->handleError($phpcsFile, $staticPtr, 'ReturnType', '"static" return type'); + + return $scopeOpener; + } + + /* + * Check other uses of static. + */ + $functionPtr = Conditions::getLastCondition($phpcsFile, $stackPtr, [\T_FUNCTION, \T_CLOSURE]); + if ($functionPtr === false || $tokens[$functionPtr]['code'] === \T_CLOSURE) { + /* + * When `false`, this code is absolutely invalid, but not something to be addressed via this sniff. + * When a closure, we're not interested in it. The closure class is final, but closures + * can be bound to other classes. This needs further research and should maybe get its own sniff. + */ + return; + } + + $ooPtr = Scopes::validDirectScope($phpcsFile, $functionPtr, $this->validOOScopes); + if ($ooPtr === false) { + // Not in an OO context. + return; + } + + if ($tokens[$ooPtr]['code'] === \T_CLASS) { + $classProps = ObjectDeclarations::getClassProperties($phpcsFile, $ooPtr); + if ($classProps['is_final'] === false) { + // Token in a non-final class. + return; + } + } + + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prevNonEmpty !== false) { + if ($tokens[$prevNonEmpty]['code'] === \T_INSTANCEOF) { + $prevPrevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevNonEmpty - 1), null, true); + $extraMsg = GetTokensAsString::compact($phpcsFile, $prevPrevNonEmpty, $stackPtr, true); + $this->handleError($phpcsFile, $stackPtr, 'InstanceOf', '"' . $extraMsg . '"'); + return; + } + + if ($tokens[$prevNonEmpty]['code'] === \T_NEW) { + $this->handleError($phpcsFile, $stackPtr, 'NewInstance', '"new static"'); + return; + } + } + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_DOUBLE_COLON) { + $nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true); + $extraMsg = GetTokensAsString::compact($phpcsFile, $stackPtr, $nextNextNonEmpty, true); + $this->handleError($phpcsFile, $stackPtr, 'ScopeResolution', '"' . $extraMsg . '"'); + return; + } + } + + /** + * Throw and potentially fix the error. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of erroneous `T_STATIC` token. + * @param string $errorCode The error code for the message. + * @param string $extraMsg Addition to the error message. + * + * @return void + */ + private function handleError($phpcsFile, $stackPtr, $errorCode, $extraMsg) + { + $fix = $phpcsFile->addFixableError( + 'Use "self" instead of "static" when using late static binding in a final OO construct. Found: %s', + $stackPtr, + $errorCode, + [$extraMsg] + ); + + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, 'self'); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/LowercaseClassResolutionKeywordSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/LowercaseClassResolutionKeywordSniff.php new file mode 100644 index 00000000..7a67e479 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/LowercaseClassResolutionKeywordSniff.php @@ -0,0 +1,106 @@ + + */ + public function register() + { + return [\T_STRING]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $content = $tokens[$stackPtr]['content']; + $contentLC = \strtolower($content); + + if ($contentLC !== 'class') { + return; + } + + $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextToken !== false && $tokens[$nextToken]['code'] === \T_OPEN_PARENTHESIS) { + // Function call or declaration for a function called "class". + return; + } + + $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prevToken === false || $tokens[$prevToken]['code'] !== \T_DOUBLE_COLON) { + return; + } + + if ($contentLC === $content) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase'); + return; + } + + $error = "The ::class keyword for class name resolution must be in lowercase. Expected: '::%s'; found: '::%s'"; + $data = [ + $contentLC, + $content, + ]; + + $errorCode = ''; + if (\strtoupper($content) === $content) { + $errorCode = 'Uppercase'; + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase'); + } else { + $errorCode = 'Mixedcase'; + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case'); + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, $contentLC); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/ModifierKeywordOrderSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/ModifierKeywordOrderSniff.php new file mode 100644 index 00000000..4b2e7183 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/ModifierKeywordOrderSniff.php @@ -0,0 +1,199 @@ + + */ + public function register() + { + return [\T_CONST]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (Scopes::isOOConstant($phpcsFile, $stackPtr) === false) { + return; + } + + $tokens = $phpcsFile->getTokens(); + $valid = Collections::constantModifierKeywords() + Tokens::$emptyTokens; + + $finalPtr = false; + $visibilityPtr = false; + + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (isset($valid[$tokens[$i]['code']]) === false) { + break; + } + + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + if ($tokens[$i]['code'] === \T_FINAL) { + $finalPtr = $i; + } else { + $visibilityPtr = $i; + } + } + + if ($finalPtr === false || $visibilityPtr === false) { + /* + * Either no modifier keywords found at all; or only one type of modifier + * keyword (final or visibility) declared, but not both. No ordering needed. + */ + return; + } + + if ($visibilityPtr < $finalPtr) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::VISIBILITY_FINAL); + } else { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::FINAL_VISIBILITY); + } + + $message = 'OO constant modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; + + switch ($this->order) { + case self::VISIBILITY_FINAL: + if ($visibilityPtr < $finalPtr) { + // Order is correct. Nothing to do. + return; + } + + $this->handleError($phpcsFile, $finalPtr, $visibilityPtr); + break; + + case self::FINAL_VISIBILITY: + default: + if ($finalPtr < $visibilityPtr) { + // Order is correct. Nothing to do. + return; + } + + $this->handleError($phpcsFile, $visibilityPtr, $finalPtr); + break; + } + } + + /** + * Throw the error and potentially fix it. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $firstKeyword The position of the first keyword found. + * @param int $secondKeyword The position of the second keyword token. + * + * @return void + */ + private function handleError(File $phpcsFile, $firstKeyword, $secondKeyword) + { + $tokens = $phpcsFile->getTokens(); + + $message = 'Constant modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; + $data = [ + $tokens[$secondKeyword]['content'] . ' ' . $tokens[$firstKeyword]['content'], + $tokens[$firstKeyword]['content'] . ' ' . $tokens[$secondKeyword]['content'], + ]; + + $fix = $phpcsFile->addFixableError($message, $firstKeyword, 'Incorrect', $data); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($secondKeyword, ''); + + // Prevent leaving behind trailing whitespace. + $i = ($secondKeyword + 1); + while ($tokens[$i]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + ++$i; + } + + // Use the original token content as the case used for keywords is not the concern of this sniff. + $phpcsFile->fixer->addContentBefore($firstKeyword, $tokens[$secondKeyword]['content'] . ' '); + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/UppercaseMagicConstantsSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/UppercaseMagicConstantsSniff.php new file mode 100644 index 00000000..348cf65e --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Constants/UppercaseMagicConstantsSniff.php @@ -0,0 +1,89 @@ + + */ + public function register() + { + return Tokens::$magicConstants; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $content = $tokens[$stackPtr]['content']; + $contentUC = \strtoupper($content); + if ($contentUC === $content) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase'); + return; + } + + $error = 'Magic constants should be in uppercase. Expected: %s; found: %s'; + $errorCode = ''; + $data = [ + $contentUC, + $content, + ]; + + if (\strtolower($content) === $content) { + $errorCode = 'Lowercase'; + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase'); + } else { + $errorCode = 'Mixedcase'; + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case'); + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, $contentUC); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowAlternativeSyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowAlternativeSyntaxSniff.php new file mode 100644 index 00000000..ab78a70a --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowAlternativeSyntaxSniff.php @@ -0,0 +1,216 @@ + + */ + public function register() + { + $targets = Collections::alternativeControlStructureSyntaxes(); + + // Don't look for elseif/else as they need to be dealt with in one go with the if. + unset($targets[\T_ELSEIF], $targets[\T_ELSE]); + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + /* + * Ignore control structures without body (i.e. single line control structures). + * This doesn't ignore _empty_ bodies. + */ + if (ControlStructures::hasBody($phpcsFile, $stackPtr, true) === false) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'single line (without body)'); + return; + } + + $tokens = $phpcsFile->getTokens(); + + /* + * Check if the control structure uses alternative syntax. + */ + if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { + // No scope opener found: inline control structure or parse error. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'inline'); + return; + } + + $opener = $tokens[$stackPtr]['scope_opener']; + $closer = $tokens[$stackPtr]['scope_closer']; + + if ($tokens[$opener]['code'] !== \T_COLON) { + // Curly brace syntax (not our concern). + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'curly braces'); + return; + } + + /* + * As of here, we *know* the control structure must be using alternative syntax and + * must have all scope openers/closers set as, in case of parse errors, PHPCS wouldn't + * have set the scope opener, even for the first `if`. + * + * Also note that alternative syntax cannot be used with `else if`, so we don't need to take that + * into account. + */ + + /* + * Determine whether there is inline HTML. + * + * For "chained" control structures (if - elseif - else), the complete control structure + * needs to be examined in one go as these cannot be changed individually, only as a complete group. + */ + $closedScopes = Collections::closedScopes(); + $find = $closedScopes; + $find[\T_INLINE_HTML] = \T_INLINE_HTML; + + $chainedIssues = []; + $hasInlineHTML = false; + $currentPtr = $stackPtr; + + do { + $opener = $tokens[$currentPtr]['scope_opener']; + $closer = $tokens[$currentPtr]['scope_closer']; + $chainedIssues[$opener] = $closer; + + if ($hasInlineHTML === true) { + // No need to search the contents, we already know there is inline HTML. + $currentPtr = $closer; + continue; + } + + $inlineHTMLPtr = $opener; + + do { + $inlineHTMLPtr = $phpcsFile->findNext($find, ($inlineHTMLPtr + 1), $closer); + if ($tokens[$inlineHTMLPtr]['code'] === \T_INLINE_HTML) { + $hasInlineHTML = true; + break; + } + + if (isset($closedScopes[$tokens[$inlineHTMLPtr]['code']], $tokens[$inlineHTMLPtr]['scope_closer'])) { + $inlineHTMLPtr = $tokens[$inlineHTMLPtr]['scope_closer']; + } + } while ($inlineHTMLPtr !== false && $inlineHTMLPtr < $closer); + + $currentPtr = $closer; + } while (isset(Collections::alternativeControlStructureSyntaxes()[$tokens[$closer]['code']]) === true); + + if ($hasInlineHTML === true) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'alternative syntax with inline HTML'); + } else { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'alternative syntax'); + } + + if ($hasInlineHTML === true && $this->allowWithInlineHTML === true) { + return; + } + + $error = 'Using control structures with the alternative syntax is not allowed'; + if ($this->allowWithInlineHTML === true) { + $error .= ' unless the control structure contains inline HTML'; + } + $error .= '. Found: %1$s(): ... end%1$s;'; + + $code = 'Found' . \ucfirst($tokens[$stackPtr]['content']); + if ($hasInlineHTML === true) { + $code .= 'WithInlineHTML'; + } + + $data = [$tokens[$stackPtr]['content']]; + + foreach ($chainedIssues as $opener => $closer) { + $fix = $phpcsFile->addFixableError($error, $opener, $code, $data); + } + + if ($fix === false) { + return; + } + + /* + * Fix all issues for this chain in one go to diminish the chance of conflicts. + */ + $phpcsFile->fixer->beginChangeset(); + + foreach ($chainedIssues as $opener => $closer) { + $phpcsFile->fixer->replaceToken($opener, '{'); + + if (isset(Collections::alternativeControlStructureSyntaxClosers()[$tokens[$closer]['code']]) === true) { + $phpcsFile->fixer->replaceToken($closer, '}'); + + $semicolon = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true); + if ($semicolon !== false && $tokens[$semicolon]['code'] === \T_SEMICOLON) { + $phpcsFile->fixer->replaceToken($semicolon, ''); + } + } else { + /* + * This must be an if/else using alternative syntax. + * The closer will be the next control structure keyword. + */ + $phpcsFile->fixer->addContentBefore($closer, '} '); + } + } + + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php new file mode 100644 index 00000000..58c81ae1 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php @@ -0,0 +1,348 @@ + + */ + public function register() + { + return [\T_ELSE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + /* + * Deal with `else if`. + */ + if (ControlStructures::isElseIf($phpcsFile, $stackPtr) === true) { + // Ignore, not our real target. + return; + } + + if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { + // Either an else without curly braces or a parse error. Ignore. + return; + } + + $outerScopeOpener = $tokens[$stackPtr]['scope_opener']; + $outerScopeCloser = $tokens[$stackPtr]['scope_closer']; + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($outerScopeOpener + 1), $outerScopeCloser, true); + if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_IF) { + // Definitely not a lonely if statement. + return; + } + + if (isset($tokens[$nextNonEmpty]['scope_closer']) === false) { + // Either a control structure without curly braces or a parse error. Ignore. + return; + } + + /* + * Find the end of an if - else chain. + */ + + $innerIfPtr = $nextNonEmpty; + $innerIfToken = $tokens[$innerIfPtr]; + $autoFixable = true; + $innerScopeCloser = $innerIfToken['scope_closer']; + + // For alternative syntax fixer only. + // Remember the individual inner scope opener and closers so the fixer doesn't need + // to do the same walking over the if/else chain again. + $innerScopes = [ + $innerIfToken['scope_opener'] => $innerScopeCloser, + ]; + + do { + /* + * Handle control structures using alternative syntax. + */ + if ($tokens[$innerScopeCloser]['code'] !== \T_CLOSE_CURLY_BRACKET) { + if ($tokens[$innerScopeCloser]['code'] === \T_ENDIF) { + $nextAfter = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($innerScopeCloser + 1), + $outerScopeCloser, + true + ); + + if ($tokens[$nextAfter]['code'] === \T_CLOSE_TAG) { + // Not "lonely" as at the very least there must be a PHP open tag before the outer closer. + return; + } + + if ($tokens[$nextAfter]['code'] === \T_SEMICOLON) { + $innerScopeCloser = $nextAfter; + } else { + // Missing semi-colon. Report, but don't auto-fix. + $autoFixable = false; + } + } else { + // This must be an else[if]. + --$innerScopeCloser; + } + } + + $innerNextNonEmpty = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($innerScopeCloser + 1), + $outerScopeCloser, + true + ); + if ($innerNextNonEmpty === false) { + // This was the last closer. + break; + } + + if ($tokens[$innerNextNonEmpty]['code'] !== \T_ELSE + && $tokens[$innerNextNonEmpty]['code'] !== \T_ELSEIF + ) { + // Found another statement after the control structure. The "if" is not lonely. + return; + } + + if (isset($tokens[$innerNextNonEmpty]['scope_closer']) === false) { + // This may still be an "else if"... + $nextAfter = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($innerNextNonEmpty + 1), + $outerScopeCloser, + true + ); + + if ($nextAfter === false + || $tokens[$nextAfter]['code'] !== \T_IF + || isset($tokens[$nextAfter]['scope_closer']) === false + ) { + // Defense in depth. Either a control structure without curly braces or a parse error. Ignore. + return; + } + + $innerNextNonEmpty = $nextAfter; + } + + $innerScopeCloser = $tokens[$innerNextNonEmpty]['scope_closer']; + $innerScopes[$tokens[$innerNextNonEmpty]['scope_opener']] = $innerScopeCloser; + } while (true); + + /* + * As of now, we know we have an error. Check if it can be auto-fixed. + */ + if ($phpcsFile->findNext(\T_WHITESPACE, ($innerScopeCloser + 1), $outerScopeCloser, true) !== false) { + // Comment between the inner and outer closers. + $autoFixable = false; + } + + if ($tokens[$innerScopeCloser]['code'] === \T_SEMICOLON) { + $hasComment = $phpcsFile->findPrevious(\T_WHITESPACE, ($innerScopeCloser - 1), null, true); + if ($tokens[$hasComment]['code'] !== \T_ENDIF) { + // Comment between the "endif" and the semi-colon. + $autoFixable = false; + } + } + + if ($tokens[$outerScopeOpener]['line'] !== $innerIfToken['line']) { + for ($startOfNextLine = ($outerScopeOpener + 1); $startOfNextLine < $innerIfPtr; $startOfNextLine++) { + if ($tokens[$outerScopeOpener]['line'] !== $tokens[$startOfNextLine]['line']) { + break; + } + } + + if ($phpcsFile->findNext(\T_WHITESPACE, $startOfNextLine, $innerIfPtr, true) !== false) { + // Comment between the inner and outer openers. + $autoFixable = false; + } + } + + if (isset($innerIfToken['parenthesis_opener'], $innerIfToken['parenthesis_closer']) === false) { + // Start/end of the condition of the if unclear. Most likely a parse error. + $autoFixable = false; + } + + /* + * Throw the error and potentially fix it. + */ + $error = 'If control structure block found as the only statement within an "else" block. Use elseif instead.'; + $code = 'Found'; + + if ($autoFixable === false) { + $phpcsFile->addError($error, $stackPtr, $code); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code); + if ($fix === false) { + return; + } + + /* + * Fix it. + */ + $outerInnerSameType = false; + if (($tokens[$outerScopeCloser]['code'] === \T_CLOSE_CURLY_BRACKET + && $tokens[$innerScopeCloser]['code'] === \T_CLOSE_CURLY_BRACKET) + || ($tokens[$outerScopeCloser]['code'] === \T_ENDIF + && $tokens[$innerScopeCloser]['code'] === \T_SEMICOLON) + ) { + $outerInnerSameType = true; + } + + $targetIsCurly = ($tokens[$outerScopeCloser]['code'] === \T_CLOSE_CURLY_BRACKET); + + $innerScopeCount = \count($innerScopes); + + $condition = GetTokensAsString::origContent($phpcsFile, ($innerIfPtr + 1), ($innerIfToken['scope_opener'] - 1)); + if ($targetIsCurly === true) { + $condition = \rtrim($condition) . ' '; + } + + $phpcsFile->fixer->beginChangeset(); + + // Remove the inner if + condition up to and including the scope opener. + for ($i = $innerIfPtr; $i <= $innerIfToken['scope_opener']; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + // Potentially remove trailing whitespace/new line if there is no comment after the inner condition. + while ($tokens[$i]['line'] === $innerIfToken['line'] + && $tokens[$i]['code'] === \T_WHITESPACE + ) { + $phpcsFile->fixer->replaceToken($i, ''); + ++$i; + } + + // Remove any potential indentation whitespace for the inner if. + if ($tokens[$outerScopeOpener]['line'] !== $innerIfToken['line'] + && $tokens[$i]['line'] !== $innerIfToken['line'] + ) { + $i = ($nextNonEmpty - 1); + while ($tokens[$i]['line'] === $innerIfToken['line'] + && $tokens[$i]['code'] === \T_WHITESPACE + ) { + $phpcsFile->fixer->replaceToken($i, ''); + --$i; + } + } + + // Remove the inner scope closer. + $phpcsFile->fixer->replaceToken($innerScopeCloser, ''); + $i = ($innerScopeCloser - 1); + + // Handle alternative syntax for the closer. + if ($tokens[$innerScopeCloser]['code'] === \T_SEMICOLON) { + // Remove potential whitespace between the "endif" and the semicolon. + while ($tokens[$i]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + --$i; + } + + // Remove the "endif". + $phpcsFile->fixer->replaceToken($i, ''); + --$i; + } + + // Remove superfluous whitespace before the inner scope closer. + while ($tokens[$i]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + --$i; + } + + // Replace the else. + $phpcsFile->fixer->replaceToken($stackPtr, 'elseif' . $condition); + + // Remove potential superfluous whitespace between the new condition and the scope opener. + $i = ($stackPtr + 1); + while ($tokens[$i]['line'] === $tokens[$stackPtr]['line'] + && $tokens[$i]['code'] === \T_WHITESPACE + ) { + $phpcsFile->fixer->replaceToken($i, ''); + ++$i; + } + + if ($outerInnerSameType === false + && $innerScopeCount > 1 + ) { + $loop = 1; + foreach ($innerScopes as $opener => $closer) { + if ($targetIsCurly === true) { + if ($loop !== 1) { + // Only handle the opener when it's not the first of the chain as that's already handled above. + $phpcsFile->fixer->replaceToken($opener, ' {'); + } + + if ($loop !== $innerScopeCount) { + // Only handle the closer when it's not the last of the chain as that's already handled above. + $phpcsFile->fixer->addContentBefore($closer, '} '); + } + } else { + if ($loop !== 1) { + // Only handle the opener when it's not the first of the chain as that's already handled above. + $phpcsFile->fixer->replaceToken($opener, ':'); + } + + if ($loop !== $innerScopeCount) { + // Only handle the closer when it's not the last of the chain as that's already handled above. + $phpcsFile->fixer->replaceToken($closer, ''); + + $j = ($closer + 1); + while ($tokens[$j]['code'] === \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($j, ''); + ++$j; + } + } + } + + ++$loop; + } + } + + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php new file mode 100644 index 00000000..fb2c39d4 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php @@ -0,0 +1,164 @@ + + */ + public function register() + { + return [ + \T_ELSE, + \T_ELSEIF, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + /* + * Check for control structures without braces and alternative syntax. + */ + $scopePtr = $stackPtr; + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + // Deal with "else if". + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$next]['code'] === \T_IF) { + $scopePtr = $next; + } + } + + if (isset($tokens[$scopePtr]['scope_opener']) === false + || $tokens[$tokens[$scopePtr]['scope_opener']]['code'] === \T_COLON + ) { + // No scope opener found or alternative syntax (not our concern). + return; + } + + /* + * Check whether the else(if) is on a new line. + */ + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prevNonEmpty === false || $tokens[$prevNonEmpty]['code'] !== \T_CLOSE_CURLY_BRACKET) { + // Parse error or mixing braced and non-braced. Not our concern. + return; + } + + if ($tokens[$prevNonEmpty]['line'] !== $tokens[$stackPtr]['line']) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + + $errorBase = \strtoupper($tokens[$stackPtr]['content']); + $error = $errorBase . ' statement must be on a new line.'; + + $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true); + + if ($prevNonWhitespace !== $prevNonEmpty) { + // Comment found between previous scope closer and the keyword. + $fix = $phpcsFile->addError($error, $stackPtr, 'NoNewLine'); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoNewLine'); + if ($fix === false) { + return; + } + + /* + * Fix it. + */ + + // Figure out the indentation for the else(if). + $indentBase = $prevNonEmpty; + if (isset($tokens[$prevNonEmpty]['scope_condition']) === true + && ($tokens[$tokens[$prevNonEmpty]['scope_condition']]['column'] === 1 + || ($tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['code'] === \T_WHITESPACE + && $tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['column'] === 1)) + ) { + // Base the indentation off the previous if/elseif if on a line by itself. + $indentBase = $tokens[$prevNonEmpty]['scope_condition']; + } + + $indent = ''; + $firstOnIndentLine = $indentBase; + if ($tokens[$firstOnIndentLine]['column'] !== 1) { + while (isset($tokens[($firstOnIndentLine - 1)]) && $tokens[--$firstOnIndentLine]['column'] !== 1); + + if ($tokens[$firstOnIndentLine]['code'] === \T_WHITESPACE) { + $indent = $tokens[$firstOnIndentLine]['content']; + + // If tabs were replaced, use the original content. + if (isset($tokens[$firstOnIndentLine]['orig_content']) === true) { + $indent = $tokens[$firstOnIndentLine]['orig_content']; + } + } + } + + $phpcsFile->fixer->beginChangeset(); + + // Remove any whitespace between the previous scope closer and the else(if). + for ($i = ($prevNonEmpty + 1); $i < $stackPtr; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->addContent($prevNonEmpty, $phpcsFile->eolChar . $indent); + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php new file mode 100644 index 00000000..c274e751 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php @@ -0,0 +1,190 @@ + + */ + private $search = [ + // Some tokens to help skip over structures we're not interested in. + \T_START_HEREDOC => \T_START_HEREDOC, + \T_START_NOWDOC => \T_START_NOWDOC, + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + $this->search += Tokens::$ooScopeTokens; + $this->search += Collections::functionDeclarationTokens(); + + return Collections::phpOpenTags(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $firstOO = null; + $firstFunction = null; + $functionCount = 0; + $OOCount = 0; + + for ($i = 0; $i < $phpcsFile->numTokens; $i++) { + // Ignore anything within square brackets. + if ($tokens[$i]['code'] !== \T_OPEN_CURLY_BRACKET + && isset($tokens[$i]['bracket_opener'], $tokens[$i]['bracket_closer']) + && $i === $tokens[$i]['bracket_opener'] + ) { + $i = $tokens[$i]['bracket_closer']; + continue; + } + + // Skip past nested arrays, function calls and arbitrary groupings. + if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS + && isset($tokens[$i]['parenthesis_closer']) + ) { + $i = $tokens[$i]['parenthesis_closer']; + continue; + } + + // Skip over potentially large docblocks. + if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG + && isset($tokens[$i]['comment_closer']) + ) { + $i = $tokens[$i]['comment_closer']; + continue; + } + + // Ignore everything else we're not interested in. + if (isset($this->search[$tokens[$i]['code']]) === false) { + continue; + } + + // Skip over structures which won't contain anything we're interested in. + if (($tokens[$i]['code'] === \T_START_HEREDOC + || $tokens[$i]['code'] === \T_START_NOWDOC + || $tokens[$i]['code'] === \T_ANON_CLASS + || $tokens[$i]['code'] === \T_CLOSURE + || $tokens[$i]['code'] === \T_FN) + && isset($tokens[$i]['scope_condition'], $tokens[$i]['scope_closer']) + && $tokens[$i]['scope_condition'] === $i + ) { + $i = $tokens[$i]['scope_closer']; + continue; + } + + // This will be either a function declaration or an OO declaration token. + if ($tokens[$i]['code'] === \T_FUNCTION) { + if (isset($firstFunction) === false) { + $firstFunction = $i; + } + + ++$functionCount; + } else { + if (isset($firstOO) === false) { + $firstOO = $i; + } + + ++$OOCount; + } + + if (isset($tokens[$i]['scope_closer']) === true) { + $i = $tokens[$i]['scope_closer']; + } + } + + if ($functionCount > 0 && $OOCount > 0) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Both function and OO declarations'); + + $reportToken = \max($firstFunction, $firstOO); + + $phpcsFile->addError( + 'A file should either contain function declarations or OO structure declarations, but not both.' + . ' Found %d function declaration(s) and %d OO structure declaration(s).' + . ' The first function declaration was found on line %d;' + . ' the first OO declaration was found on line %d', + $reportToken, + 'Mixed', + [ + $functionCount, + $OOCount, + $tokens[$firstFunction]['line'], + $tokens[$firstOO]['line'], + ] + ); + } elseif ($functionCount > 0) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only function(s)'); + } elseif ($OOCount > 0) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only OO structure(s)'); + } else { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Neither'); + } + + // Ignore the rest of the file. + return ($phpcsFile->numTokens + 1); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/NoLongClosuresSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/NoLongClosuresSniff.php new file mode 100644 index 00000000..a9ccf78b --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/NoLongClosuresSniff.php @@ -0,0 +1,233 @@ + + */ + public function register() + { + return [\T_CLOSURE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $this->recommendedLines = (int) $this->recommendedLines; + $this->maxLines = (int) $this->maxLines; + + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { + // Live coding/parse error. Shouldn't be possible as in that case tokenizer won't retokenize to T_CLOSURE. + return; // @codeCoverageIgnore + } + + $opener = $tokens[$stackPtr]['scope_opener']; + $closer = $tokens[$stackPtr]['scope_closer']; + + $currentLine = $tokens[$opener]['line']; + $closerLine = $tokens[$closer]['line']; + + $codeLines = 0; + $commentLines = 0; + $blankLines = 0; + + // Check whether the line of the scope opener needs to be counted, but ignore trailing comments on that line. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true); + if ($firstNonEmpty !== false && $tokens[$firstNonEmpty]['line'] === $currentLine) { + ++$codeLines; + } + + // Check whether the line of the scope closer needs to be counted. + if ($closerLine !== $currentLine) { + $hasCommentTokens = false; + $hasCodeTokens = false; + for ($i = ($closer - 1); $tokens[$i]['line'] === $closerLine && $i > $opener; $i--) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { + $hasCodeTokens = true; + } elseif (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { + $hasCommentTokens = true; + } + } + + if ($hasCodeTokens === true) { + ++$codeLines; + } elseif ($hasCommentTokens === true) { + ++$commentLines; + } + } + + // We've already examined the opener line, so move to the next line. + for ($i = ($opener + 1); $tokens[$i]['line'] === $currentLine && $i < $closer; $i++); + $currentLine = $tokens[$i]['line']; + + // Walk tokens. + while ($currentLine !== $closerLine) { + $hasCommentTokens = false; + $hasCodeTokens = false; + + while ($tokens[$i]['line'] === $currentLine) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { + $hasCodeTokens = true; + } elseif (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { + $hasCommentTokens = true; + } + + ++$i; + } + + if ($hasCodeTokens === true) { + ++$codeLines; + } elseif ($hasCommentTokens === true) { + ++$commentLines; + } else { + // Only option left is that this is an empty line. + ++$blankLines; + } + + $currentLine = $tokens[$i]['line']; + } + + $nonBlankLines = ($codeLines + $commentLines); + $totalLines = ($codeLines + $commentLines + $blankLines); + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_CODE, $codeLines . ' lines'); + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_COMMENTS, $nonBlankLines . ' lines'); + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_ALL, $totalLines . ' lines'); + + $lines = $codeLines; + if ($this->ignoreCommentLines === false) { + $lines += $commentLines; + } + if ($this->ignoreEmptyLines === false) { + $lines += $blankLines; + } + + $errorSuffix = ' Declare a named function instead. Found closure containing %s lines'; + + if ($lines > $this->maxLines) { + $phpcsFile->addError( + 'Closures which are longer than %s lines are forbidden.' . $errorSuffix, + $stackPtr, + 'ExceedsMaximum', + [$this->maxLines, $lines] + ); + + return; + } + + if ($lines > $this->recommendedLines) { + $phpcsFile->addWarning( + 'It is recommended for closures to contain %s lines or less.' . $errorSuffix, + $stackPtr, + 'ExceedsRecommended', + [$this->recommendedLines, $lines] + ); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/RequireFinalMethodsInTraitsSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/RequireFinalMethodsInTraitsSniff.php new file mode 100644 index 00000000..505c19b3 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/FunctionDeclarations/RequireFinalMethodsInTraitsSniff.php @@ -0,0 +1,120 @@ + + */ + public function register() + { + return [\T_FUNCTION]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { + // Parse error/live coding. + return; + } + + $scopePtr = Scopes::validDirectScope($phpcsFile, $stackPtr, \T_TRAIT); + if ($scopePtr === false) { + // Not a trait method. + return; + } + + $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); + if ($methodProps['scope'] === 'private') { + // Private methods can't be final. + return; + } + + if ($methodProps['is_final'] === true) { + // Already final, nothing to do. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); + return; + } + + if ($methodProps['is_abstract'] === true) { + // Abstract classes can't be final. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); + + $methodName = FunctionDeclarations::getName($phpcsFile, $stackPtr); + $magic = ''; + $code = 'NonFinalMethodFound'; + if (FunctionDeclarations::isMagicMethodName($methodName) === true) { + // Use separate error code for magic methods. + $magic = 'magic '; + $code = 'NonFinalMagicMethodFound'; + } + + $data = [ + $methodProps['scope'], + $magic, + $methodName, + ObjectDeclarations::getName($phpcsFile, $scopePtr), + ]; + + $fix = $phpcsFile->addFixableError( + 'The non-abstract, %s %smethod "%s()" in trait %s should be declared as final.', + $stackPtr, + $code, + $data + ); + + if ($fix === true) { + $phpcsFile->fixer->addContentBefore($stackPtr, 'final '); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php new file mode 100644 index 00000000..de7447e0 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php @@ -0,0 +1,71 @@ + + */ + public function register() + { + return [\T_LIST]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $openClose = Lists::getOpenClose($phpcsFile, $stackPtr); + if ($openClose === false) { + // Live coding or parse error. + return; + } + + $fix = $phpcsFile->addFixableError('Long list syntax is not allowed', $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($stackPtr, ''); + $phpcsFile->fixer->replaceToken($opener, '['); + $phpcsFile->fixer->replaceToken($closer, ']'); + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php new file mode 100644 index 00000000..af2a9489 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php @@ -0,0 +1,86 @@ + + */ + public function register() + { + return Collections::listOpenTokensBC(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] === \T_LIST) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + return; + } + + $openClose = Lists::getOpenClose($phpcsFile, $stackPtr); + + if ($openClose === false) { + // Not a short list, live coding or parse error. + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + + $fix = $phpcsFile->addFixableError('Short list syntax is not allowed', $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($opener, 'list('); + $phpcsFile->fixer->replaceToken($closer, ')'); + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php new file mode 100644 index 00000000..3b6ad982 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php @@ -0,0 +1,81 @@ + + */ + public function register() + { + return [\T_NAMESPACE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { + // Namespace operator, not a declaration; or live coding/parse error. + return; + } + + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['scope_condition']) === false + || $tokens[$stackPtr]['scope_condition'] !== $stackPtr + ) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + + $phpcsFile->addError( + 'Namespace declarations using the curly brace syntax are not allowed.', + $stackPtr, + 'Forbidden' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowDeclarationWithoutNameSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowDeclarationWithoutNameSniff.php new file mode 100644 index 00000000..5348e707 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/DisallowDeclarationWithoutNameSniff.php @@ -0,0 +1,80 @@ + + */ + public function register() + { + return [\T_NAMESPACE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $name = Namespaces::getDeclaredName($phpcsFile, $stackPtr); + if ($name === false) { + // Use of the namespace keyword as an operator or live coding/parse error. + return; + } + + if ($name !== '') { + // Named namespace. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + + // Namespace declaration without namespace name (= global namespace). + $phpcsFile->addError( + 'Namespace declarations without a namespace name are not allowed.', + $stackPtr, + 'Forbidden' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php new file mode 100644 index 00000000..78fa3261 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php @@ -0,0 +1,81 @@ + + */ + public function register() + { + return [\T_NAMESPACE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { + // Namespace operator, not a declaration; or live coding/parse error. + return; + } + + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['scope_condition']) === true + && $tokens[$stackPtr]['scope_condition'] === $stackPtr + ) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); + + $phpcsFile->addError( + 'Namespace declarations without curly braces are not allowed.', + $stackPtr, + 'Forbidden' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php new file mode 100644 index 00000000..65f3e589 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php @@ -0,0 +1,96 @@ + + */ + public function register() + { + return [\T_NAMESPACE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $fileName = $phpcsFile->getFilename(); + if ($this->currentFile !== $fileName) { + // Reset the properties for each new file. + $this->currentFile = $fileName; + $this->declarationSeen = false; + } + + if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { + // Namespace operator, not a declaration; or live coding/parse error. + return; + } + + if ($this->declarationSeen === false) { + // This is the first namespace declaration in the file. + $this->declarationSeen = $stackPtr; + return; + } + + $tokens = $phpcsFile->getTokens(); + + // OK, so this is a file with multiple namespace declarations. + $phpcsFile->addError( + 'There should be only one namespace declaration per file. The first declaration was found on line %d', + $stackPtr, + 'MultipleFound', + [$tokens[$this->declarationSeen]['line']] + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/NamingConventions/NoReservedKeywordParameterNamesSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/NamingConventions/NoReservedKeywordParameterNamesSniff.php new file mode 100644 index 00000000..7815e7e0 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/NamingConventions/NoReservedKeywordParameterNamesSniff.php @@ -0,0 +1,190 @@ + Key is the lowercased keyword, value the "proper" cased keyword. + */ + private $reservedNames = [ + 'abstract' => 'abstract', + 'and' => 'and', + 'array' => 'array', + 'as' => 'as', + 'break' => 'break', + 'callable' => 'callable', + 'case' => 'case', + 'catch' => 'catch', + 'class' => 'class', + 'clone' => 'clone', + 'const' => 'const', + 'continue' => 'continue', + 'declare' => 'declare', + 'default' => 'default', + 'die' => 'die', + 'do' => 'do', + 'echo' => 'echo', + 'else' => 'else', + 'elseif' => 'elseif', + 'empty' => 'empty', + 'enddeclare' => 'enddeclare', + 'endfor' => 'endfor', + 'endforeach' => 'endforeach', + 'endif' => 'endif', + 'endswitch' => 'endswitch', + 'endwhile' => 'endwhile', + 'enum' => 'enum', + 'eval' => 'eval', + 'exit' => 'exit', + 'extends' => 'extends', + 'final' => 'final', + 'finally' => 'finally', + 'fn' => 'fn', + 'for' => 'for', + 'foreach' => 'foreach', + 'function' => 'function', + 'global' => 'global', + 'goto' => 'goto', + 'if' => 'if', + 'implements' => 'implements', + 'include' => 'include', + 'include_once' => 'include_once', + 'instanceof' => 'instanceof', + 'insteadof' => 'insteadof', + 'interface' => 'interface', + 'isset' => 'isset', + 'list' => 'list', + 'match' => 'match', + 'namespace' => 'namespace', + 'new' => 'new', + 'or' => 'or', + 'print' => 'print', + 'private' => 'private', + 'protected' => 'protected', + 'public' => 'public', + 'readonly' => 'readonly', + 'require' => 'require', + 'require_once' => 'require_once', + 'return' => 'return', + 'static' => 'static', + 'switch' => 'switch', + 'throw' => 'throw', + 'trait' => 'trait', + 'try' => 'try', + 'unset' => 'unset', + 'use' => 'use', + 'var' => 'var', + 'while' => 'while', + 'xor' => 'xor', + 'yield' => 'yield', + '__class__' => '__CLASS__', + '__dir__' => '__DIR__', + '__file__' => '__FILE__', + '__function__' => '__FUNCTION__', + '__line__' => '__LINE__', + '__method__' => '__METHOD__', + '__namespace__' => '__NAMESPACE__', + '__trait__' => '__TRAIT__', + 'int' => 'int', + 'float' => 'float', + 'bool' => 'bool', + 'string' => 'string', + 'true' => 'true', + 'false' => 'false', + 'null' => 'null', + 'void' => 'void', + 'iterable' => 'iterable', + 'object' => 'object', + 'resource' => 'resource', + 'mixed' => 'mixed', + 'numeric' => 'numeric', + 'never' => 'never', + + /* + * Not reserved keywords, but equally confusing when used in the context of function calls + * with named parameters. + */ + 'parent' => 'parent', + 'self' => 'self', + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return Collections::functionDeclarationTokens(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + // Get all parameters from method signature. + $parameters = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); + if (empty($parameters)) { + return; + } + + $message = 'It is recommended not to use reserved keyword "%s" as function parameter name. Found: %s'; + + foreach ($parameters as $param) { + $name = \ltrim($param['name'], '$'); + $nameLC = \strtolower($name); + if (isset($this->reservedNames[$nameLC]) === true) { + $errorCode = $nameLC . 'Found'; + $data = [ + $this->reservedNames[$nameLC], + $param['name'], + ]; + + $phpcsFile->addWarning($message, $param['token'], $errorCode, $data); + } + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/OOStructures/AlphabeticExtendsImplementsSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/OOStructures/AlphabeticExtendsImplementsSniff.php new file mode 100644 index 00000000..4d765c2c --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/OOStructures/AlphabeticExtendsImplementsSniff.php @@ -0,0 +1,275 @@ + + * class Foo implements \Vendor\DiffIterator, My\Count, DateTimeInterface {} + * + * + * If sorted using the "name" sort-order, the sniff looks just at the interface name, i.e. + * `DiffIterator`, `Count` and `DateTimeInterface`, which for this example would mean + * the correct order would be `My\Count, DateTimeInterface, \Vendor\DiffIterator`. + * + * If sorted using the "full" sort-order, the sniff will look at the full name as used + * in the `implements` statement, without leading backslashes. + * For the example above, this would mean that the correct order would be: + * `DateTimeInterface, My\Count, \Vendor\DiffIterator`. + * + * @since 1.0.0 + * + * @var string + */ + public $orderby = 'name'; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return (Collections::ooCanExtend() + Collections::ooCanImplement()); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + /* + * Validate the setting. + */ + if ($this->orderby !== 'full') { + // Use the default. + $this->orderby = 'name'; + } + $metricNameAlpha = \sprintf(self::METRIC_NAME_ALPHA, $this->orderby); + + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + // Parse error or live coding. Ignore. + return; + } + + $scopeOpener = $tokens[$stackPtr]['scope_opener']; + + /* + * Get the names. + */ + if (isset(Collections::ooCanImplement()[$tokens[$stackPtr]['code']]) === true) { + $names = ObjectDeclarations::findImplementedInterfaceNames($phpcsFile, $stackPtr); + } else { + $names = ObjectDeclarations::findExtendedInterfaceNames($phpcsFile, $stackPtr); + } + + if (\is_array($names) === false) { + // Class/interface/enum doesn't extend or implement. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_COUNT, 0); + $phpcsFile->recordMetric($stackPtr, $metricNameAlpha, 'n/a'); + return; + } + + $count = \count($names); + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_COUNT, $count); + + if ($count < 2) { + // Nothing to sort. + $phpcsFile->recordMetric($stackPtr, $metricNameAlpha, 'n/a'); + return; + } + + /* + * Check the order. + */ + if ($this->orderby === 'name') { + $sorted = $this->sortByName($names); + } else { + $sorted = $this->sortByFull($names); + } + + if ($sorted === $names) { + // Order is already correct. + $phpcsFile->recordMetric($stackPtr, $metricNameAlpha, 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, $metricNameAlpha, 'no'); + + /* + * Throw the error. + */ + $keyword = \T_IMPLEMENTS; + if (isset(Collections::ooCanImplement()[$tokens[$stackPtr]['code']]) === false) { + $keyword = \T_EXTENDS; + } + + $fixable = true; + $keywordPtr = $phpcsFile->findNext($keyword, ($stackPtr + 1), $scopeOpener); + $hasComment = $phpcsFile->findNext(Tokens::$commentTokens, ($keywordPtr + 1), $scopeOpener); + if ($hasComment !== false) { + $fixable = false; + } + + $error = "The interface names in a \"%s %s\" statement should be ordered alphabetically.\n"; + $error .= 'Expected: %s; Found: %s'; + $code = \ucfirst(\strtolower($tokens[$keywordPtr]['content'])) . 'WrongOrder'; + $data = [ + $tokens[$stackPtr]['content'], + $tokens[$keywordPtr]['content'], + \implode(', ', $names), + \implode(', ', $sorted), + ]; + + if ($fixable === false) { + $code .= 'WithComments'; + $phpcsFile->addError($error, $keywordPtr, $code, $data); + return; + } + + // OK, so we appear to have a fixable error. + $fix = $phpcsFile->addFixableError($error, $keywordPtr, $code, $data); + if ($fix === false) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + + // Remove the complete previous extends/implements part. + for ($i = ($keywordPtr + 1); $i < $scopeOpener; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->addContent($keywordPtr, ' ' . \implode(', ', $sorted) . ' '); + + $phpcsFile->fixer->endChangeset(); + } + + /** + * Sort an array of potentially mixed qualified and unqualified names by the interface name. + * + * @since 1.0.0 + * + * @param string[] $names Interface names, potentially mixed qualified and unqualified. + * + * @return string[] + */ + protected function sortByName(array $names) + { + $getLastName = function ($name) { + $last = \strrchr($name, '\\'); + if ($last === false) { + $last = $name; + } else { + $last = \substr($last, 1); + } + + return $last; + }; + + return $this->sortNames($names, $getLastName); + } + + /** + * Sort an array of potentially mixed qualified and unqualified names by the full name. + * + * @since 1.0.0 + * + * @param string[] $names Interface names, potentially mixed qualified and unqualified. + * + * @return string[] + */ + protected function sortByFull(array $names) + { + $trimLeadingBackslash = function ($name) { + return \ltrim($name, '\\'); + }; + + return $this->sortNames($names, $trimLeadingBackslash); + } + + /** + * Sort an array of names. + * + * @since 1.0.0 + * + * @param string[] $names Interface names, potentially mixed qualified and unqualified. + * @param callable $prepareNames Function to call to prepare the names before sorting. + * + * @return string[] + */ + private function sortNames(array $names, callable $prepareNames) + { + $preppedNames = \array_map($prepareNames, $names); + $names = \array_combine($names, $preppedNames); + + \natcasesort($names); + + return \array_keys($names); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowLogicalAndOrSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowLogicalAndOrSniff.php new file mode 100644 index 00000000..19e49717 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowLogicalAndOrSniff.php @@ -0,0 +1,112 @@ + + */ + private $metricType = [ + \T_LOGICAL_AND => 'logical (and/or)', + \T_LOGICAL_OR => 'logical (and/or)', + \T_BOOLEAN_AND => 'boolean (&&/||)', + \T_BOOLEAN_OR => 'boolean (&&/||)', + ]; + + /** + * The tokens this sniff targets with error code and replacements. + * + * @since 1.0.0 + * + * @var array> + */ + private $targetTokenInfo = [ + \T_LOGICAL_AND => [ + 'error_code' => 'LogicalAnd', + 'replacement' => '&&', + ], + \T_LOGICAL_OR => [ + 'error_code' => 'LogicalOr', + 'replacement' => '||', + ], + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return \array_keys($this->metricType); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $tokenCode = $tokens[$stackPtr]['code']; + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, $this->metricType[$tokenCode]); + + if (isset($this->targetTokenInfo[$tokenCode]) === false) { + // Already using boolean operator. + return; + } + + $error = 'Using logical operators is not allowed. Expected: "%s"; Found: "%s"'; + $data = [ + $this->targetTokenInfo[$tokenCode]['replacement'], + $tokens[ $stackPtr ]['content'], + ]; + + $phpcsFile->addError($error, $stackPtr, $this->targetTokenInfo[$tokenCode]['error_code'], $data); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowShortTernarySniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowShortTernarySniff.php new file mode 100644 index 00000000..37a39a18 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowShortTernarySniff.php @@ -0,0 +1,76 @@ + + */ + public function register() + { + return [\T_INLINE_THEN]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (Operators::isShortTernary($phpcsFile, $stackPtr) === false) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'long'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'short'); + + $phpcsFile->addError( + 'Using short ternaries is not allowed as they are rarely used correctly', + $stackPtr, + 'Found' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowStandalonePostIncrementDecrementSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowStandalonePostIncrementDecrementSniff.php new file mode 100644 index 00000000..b8538882 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/DisallowStandalonePostIncrementDecrementSniff.php @@ -0,0 +1,197 @@ + + */ + private $allowedTokens = [ + \T_VARIABLE => \T_VARIABLE, + ]; + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + $this->allowedTokens += Collections::ooHierarchyKeywords(); + $this->allowedTokens += Collections::objectOperators(); + $this->allowedTokens += Collections::namespacedNameTokens(); + + /* + * Remove nullsafe object operator. In/decrement not allowed in write context, + * so ignore. + */ + unset($this->allowedTokens[\T_NULLSAFE_OBJECT_OPERATOR]); + + return Collections::incrementDecrementOperators(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return; + } + + $start = BCFile::findStartOfStatement($phpcsFile, $stackPtr); + $end = BCFile::findEndOfStatement($phpcsFile, $stackPtr); + + if (isset(Collections::incrementDecrementOperators()[$tokens[$end]['code']])) { + // Statement ends on a PHP close tag, set the end pointer to the close tag. + $end = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true); + } + + if ($tokens[$end]['code'] !== \T_SEMICOLON + && $tokens[$end]['code'] !== \T_CLOSE_TAG + ) { + // Not a stand-alone statement. + return $end; + } + + $counter = 0; + $lastCode = null; + $operators = Collections::incrementDecrementOperators(); + for ($i = $start; $i < $end; $i++) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + if (isset($operators[$tokens[$i]['code']]) === true) { + $lastCode = $tokens[$i]['code']; + ++$counter; + continue; + } + + if (isset($this->allowedTokens[$tokens[$i]['code']]) === true) { + $lastCode = $tokens[$i]['code']; + continue; + } + + if ($tokens[$i]['code'] === \T_OPEN_SQUARE_BRACKET + && isset($tokens[$i]['bracket_closer']) + && ($lastCode === \T_VARIABLE || $lastCode === \T_STRING) + ) { + // Array access. + $i = $tokens[$i]['bracket_closer']; + continue; + } + + // Came across an unexpected token. This is (probably) not a stand-alone statement. + return $end; + } + + if ($counter > 1) { + $phpcsFile->addWarning( + 'Using multiple increment/decrement operators in a stand-alone statement is strongly discouraged.' + . ' Found: %s', + $stackPtr, + 'MultipleOperatorsFound', + [GetTokensAsString::compact($phpcsFile, $start, ($end - 1), true)] + ); + + return $end; + } + + $type = 'increment'; + if ($tokens[$stackPtr]['code'] === \T_DEC) { + $type = 'decrement'; + } + + $lastNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($end - 1), $start, true); + if ($start === $stackPtr && $lastNonEmpty !== $stackPtr) { + // This is already pre-in/decrement. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'pre-' . $type); + return $end; + } + + if ($lastNonEmpty === false || $lastNonEmpty === $start || $lastNonEmpty !== $stackPtr) { + // Parse error or otherwise unsupported syntax. Ignore. + return $end; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'post-' . $type); + + $error = 'Stand-alone post-%1$s statement found. Use pre-%1$s instead: %2$s.'; + $errorCode = 'Post' . \ucfirst($type) . 'Found'; + $replacement = $tokens[$stackPtr]['content']; + $replacement .= GetTokensAsString::compact($phpcsFile, $start, ($lastNonEmpty - 1), true); + $data = [ + $type, + $replacement, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($stackPtr, ''); + $phpcsFile->fixer->addContentBefore($start, $tokens[$stackPtr]['content']); + $phpcsFile->fixer->endChangeset(); + } + + return $end; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/StrictComparisonsSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/StrictComparisonsSniff.php new file mode 100644 index 00000000..c828df9e --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/StrictComparisonsSniff.php @@ -0,0 +1,116 @@ + + */ + private $metricType = [ + \T_IS_EQUAL => 'loose', + \T_IS_NOT_EQUAL => 'loose', + \T_IS_IDENTICAL => 'strict', + \T_IS_NOT_IDENTICAL => 'strict', + ]; + + /** + * The tokens this sniff targets with error code and replacements. + * + * @since 1.0.0 + * + * @var array> + */ + private $targetTokenInfo = [ + \T_IS_EQUAL => [ + 'error_code' => 'LooseEqual', + 'replacement' => '===', + ], + \T_IS_NOT_EQUAL => [ + 'error_code' => 'LooseNotEqual', + 'replacement' => '!==', + ], + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return \array_keys($this->metricType); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $tokenCode = $tokens[$stackPtr]['code']; + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, $this->metricType[$tokenCode]); + + if (isset($this->targetTokenInfo[$tokenCode]) === false) { + // Already using strict comparison operator. + return; + } + + $error = 'Loose comparisons are not allowed. Expected: "%s"; Found: "%s"'; + $data = [ + $this->targetTokenInfo[$tokenCode]['replacement'], + $tokens[ $stackPtr ]['content'], + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $this->targetTokenInfo[$tokenCode]['error_code'], $data); + if ($fix === false) { + return; + } + + $phpcsFile->fixer->replaceToken($stackPtr, $this->targetTokenInfo[$tokenCode]['replacement']); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php new file mode 100644 index 00000000..440ecc86 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php @@ -0,0 +1,85 @@ + + */ + public function register() + { + return [ + \T_TYPE_UNION, + \T_TYPE_INTERSECTION, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $type = ($tokens[$stackPtr]['code'] === \T_TYPE_UNION) ? 'union' : 'intersection'; + $code = \ucfirst($type) . 'Type'; + + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $prevNonEmpty, + 0, // Expected spaces. + 'Expected %s before the ' . $type . ' type separator. Found: %s', + $code . 'SpacesBefore', + 'error', + 0, // Severity. + 'Space before ' . $type . ' type separator' + ); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $nextNonEmpty, + 0, // Expected spaces. + 'Expected %s after the ' . $type . ' type separator. Found: %s', + $code . 'SpacesAfter', + 'error', + 0, // Severity. + 'Space after ' . $type . ' type separator' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php new file mode 100644 index 00000000..cbccb4c6 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php @@ -0,0 +1,101 @@ + + */ + public function register() + { + return [\T_OPEN_TAG_WITH_ECHO]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + for ($endOfStatement = ($stackPtr + 1); $endOfStatement < $phpcsFile->numTokens; $endOfStatement++) { + if ($tokens[$endOfStatement]['code'] === \T_CLOSE_TAG + || $tokens[$endOfStatement]['code'] === \T_SEMICOLON + ) { + break; + } + + // Skip over anything within parenthesis. + if ($tokens[$endOfStatement]['code'] === \T_OPEN_PARENTHESIS + && isset($tokens[$endOfStatement]['parenthesis_closer']) + ) { + $endOfStatement = $tokens[$endOfStatement]['parenthesis_closer']; + } + } + + if ($endOfStatement === $phpcsFile->numTokens + || $tokens[$endOfStatement]['code'] === \T_CLOSE_TAG + ) { + return; + } + + // Semi-colon, so check for any code between it and the close tag. + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($endOfStatement + 1), null, true); + if ($nextNonEmpty === false + || $tokens[$nextNonEmpty]['code'] === \T_CLOSE_TAG + ) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'Only one statement is allowed when using short open echo PHP tags.' + . ' Use the "fixer->replaceToken($stackPtr, 'fixer->replaceToken($stackPtr, ' + */ + public function register() + { + return [\T_USE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { + // Closure or trait use statement. Bow out. + return; + } + + $useStatements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); + + $ooCount = \count($useStatements['name']); + $functionCount = \count($useStatements['function']); + $constantCount = \count($useStatements['const']); + $totalCount = $ooCount + $functionCount + $constantCount; + + if ($totalCount === 0) { + // There must have been a parse error. Bow out. + return; + } + + // End of statement will always be found, otherwise the import statement parsing would have failed. + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + $groupStart = $phpcsFile->findNext(\T_OPEN_USE_GROUP, ($stackPtr + 1), $endOfStatement); + + if ($groupStart === false) { + // Not a group use statement. Just record the metric. + if ($totalCount === 1) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'single import'); + } else { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'multi import'); + } + + return; + } + + if ($totalCount === 1 + || ($ooCount !== 0 && $functionCount === 0 && $constantCount === 0) + || ($ooCount === 0 && $functionCount !== 0 && $constantCount === 0) + || ($ooCount === 0 && $functionCount === 0 && $constantCount !== 0) + ) { + // Not a *mixed* group use statement. + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'group use, single type'); + return; + } + + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'group use, multi type'); + + // Build up the error message. + $foundPhrases = []; + if ($ooCount > 1) { + $foundPhrases[] = \sprintf('%d namespaces/OO names', $ooCount); + } elseif ($ooCount === 1) { + $foundPhrases[] = \sprintf('%d namespace/OO name', $ooCount); + } + + if ($functionCount > 1) { + $foundPhrases[] = \sprintf('%d functions', $functionCount); + } elseif ($functionCount === 1) { + $foundPhrases[] = \sprintf('%d function', $functionCount); + } + + if ($constantCount > 1) { + $foundPhrases[] = \sprintf('%d constants', $constantCount); + } elseif ($constantCount === 1) { + $foundPhrases[] = \sprintf('%d constant', $constantCount); + } + + if (\count($foundPhrases) === 2) { + $found = \implode(' and ', $foundPhrases); + } else { + $found = \array_shift($foundPhrases) . ', '; + $found .= \implode(' and ', $foundPhrases); + } + + $error = 'Group use statements should import one type of construct.' + . ' Mixed group use statement found importing %s.'; + $code = 'Found'; + $data = [$found]; + + $hasComment = $phpcsFile->findNext(Tokens::$commentTokens, ($stackPtr + 1), $endOfStatement); + if ($hasComment !== false) { + // Don't attempt to auto-fix is there are comments or PHPCS annotations in the statement. + $phpcsFile->addError($error, $stackPtr, $code, $data); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data); + + if ($fix === false) { + return; + } + + /* + * Fix it. + * + * This fixer complies with the following (arbitrary) requirements: + * - It will re-use the original base "group" name, i.e. the part before \{. + * - It take take aliases into account, but only when something is aliased to a different name. + * Aliases re-using the original name will be removed. + * - The fix will not add a trailing comma after the last group use sub-statement. + * This is a PHP 7.2+ feature. + * If a standard wants to enforce trailing commas, they should use a separate sniff for that. + * - If there is only 1 statement of a certain type, the replacement will be a single + * import use statement, not a group use statement. + */ + + $phpcsFile->fixer->beginChangeset(); + + // Ensure that a potential close PHP tag ending the statement is not removed. + $tokens = $phpcsFile->getTokens(); + $endRemoval = $endOfStatement; + if ($tokens[$endOfStatement]['code'] !== \T_SEMICOLON) { + $endRemoval = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($endOfStatement - 1), null, true); + } + + // Remove old statement with the exception of the `use` keyword. + for ($i = ($stackPtr + 1); $i <= $endRemoval; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + // Build up the new use import statements. + $newStatements = []; + + $useIndent = \str_repeat(' ', ($tokens[$stackPtr]['column'] - 1)); + $insideIndent = $useIndent . \str_repeat(' ', 4); + + $baseGroupName = GetTokensAsString::noEmpties($phpcsFile, ($stackPtr + 1), ($groupStart - 1)); + + foreach ($useStatements as $type => $statements) { + $count = \count($statements); + if ($count === 0) { + continue; + } + + $typeName = $type . ' '; + if ($type === 'name') { + $typeName = ''; + } + + if ($count === 1) { + $fqName = \reset($statements); + $alias = \key($statements); + + $newStatement = 'use ' . $typeName . $fqName; + + $unqualifiedName = \ltrim(\substr($fqName, \strrpos($fqName, '\\')), '\\'); + if ($unqualifiedName !== $alias) { + $newStatement .= ' as ' . $alias; + } + + $newStatement .= ';'; + + $newStatements[] = $newStatement; + continue; + } + + // Multiple statements, add a single-type group use statement. + $newStatement = 'use ' . $typeName . $baseGroupName . '{' . $phpcsFile->eolChar; + + foreach ($statements as $alias => $fqName) { + $partialName = \str_replace($baseGroupName, '', $fqName); + $newStatement .= $insideIndent . $partialName; + + $unqualifiedName = \ltrim(\substr($partialName, \strrpos($partialName, '\\')), '\\'); + if ($unqualifiedName !== $alias) { + $newStatement .= ' as ' . $alias; + } + + $newStatement .= ',' . $phpcsFile->eolChar; + } + + // Remove trailing comma after last statement as that's PHP 7.2+. + $newStatement = \rtrim($newStatement, ',' . $phpcsFile->eolChar); + + $newStatement .= $phpcsFile->eolChar . $useIndent . '};'; + $newStatements[] = $newStatement; + } + + $replacement = \implode($phpcsFile->eolChar . $useIndent, $newStatements); + + $phpcsFile->fixer->replaceToken($stackPtr, $replacement); + + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php new file mode 100644 index 00000000..8dcf8ddb --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php @@ -0,0 +1,211 @@ + + */ + public function register() + { + return [ + \T_USE, + \T_NAMESPACE, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $file = $phpcsFile->getFilename(); + if ($file !== $this->currentFile) { + // Reset the current namespace for each new file. + $this->currentFile = $file; + $this->currentNamespace = ''; + } + + $tokens = $phpcsFile->getTokens(); + + // Get the name of the current namespace. + if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) { + $namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr); + if ($namespaceName !== false) { + $this->currentNamespace = $namespaceName; + } + + return; + } + + // Ok, so this is a T_USE token. + try { + $statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); + } catch (RuntimeException $e) { + // Not an import use statement. Bow out. + return; + } + + if (empty($statements['name'])) { + // No class/trait/interface/enum import statements found. + return; + } + + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['name'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; // @codeCoverageIgnore + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching. + continue; + } + + break; + } while (true); + + /* + * Build the error message and code. + * + * Check whether this is a non-namespaced (global) import and check whether this is an + * import from within the same namespace. + * + * Takes incorrect use statements with leading backslash into account. + * Takes case-INsensitivity of namespaces names into account. + * + * The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code + * in case this is a non-namespaced file. + */ + + $error = 'Use import statements for class/interface/trait/enum%s are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $errorCode = 'Found'; + $data = [ + '', + $fullName, + ]; + + $globalNamespace = false; + $sameNamespace = false; + if (\strpos($fullName, '\\', 1) === false) { + $globalNamespace = true; + $errorCode = 'FromGlobalNamespace'; + $data[0] = ' from the global namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace'); + } elseif ($this->currentNamespace !== '' + && (\stripos($fullName, $this->currentNamespace . '\\') === 0 + || \stripos($fullName, '\\' . $this->currentNamespace . '\\') === 0) + ) { + $sameNamespace = true; + $errorCode = 'FromSameNamespace'; + $data[0] = ' from the same namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace'); + } + + $hasAlias = false; + $lastLeaf = \strtolower(\substr($fullName, -(\strlen($alias) + 1))); + $aliasLC = \strtolower($alias); + if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) { + $hasAlias = true; + $error .= ' with alias: "%s"'; + $errorCode .= 'WithAlias'; + $data[] = $alias; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias'); + } + + if ($errorCode === 'Found') { + $errorCode = 'FoundWithoutAlias'; + } + + $phpcsFile->addError($error, $reportPtr, $errorCode, $data); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php new file mode 100644 index 00000000..44388f6a --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php @@ -0,0 +1,211 @@ + + */ + public function register() + { + return [ + \T_USE, + \T_NAMESPACE, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $file = $phpcsFile->getFilename(); + if ($file !== $this->currentFile) { + // Reset the current namespace for each new file. + $this->currentFile = $file; + $this->currentNamespace = ''; + } + + $tokens = $phpcsFile->getTokens(); + + // Get the name of the current namespace. + if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) { + $namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr); + if ($namespaceName !== false) { + $this->currentNamespace = $namespaceName; + } + + return; + } + + // Ok, so this is a T_USE token. + try { + $statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); + } catch (RuntimeException $e) { + // Not an import use statement. Bow out. + return; + } + + if (empty($statements['const'])) { + // No import statements for constants found. + return; + } + + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['const'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; // @codeCoverageIgnore + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching. + continue; + } + + break; + } while (true); + + /* + * Build the error message and code. + * + * Check whether this is a non-namespaced (global) import and check whether this is an + * import from within the same namespace. + * + * Takes incorrect use statements with leading backslash into account. + * Takes case-INsensitivity of namespaces names into account. + * + * The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code + * in case this is a non-namespaced file. + */ + + $error = 'Use import statements for constants%s are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $errorCode = 'Found'; + $data = [ + '', + $fullName, + ]; + + $globalNamespace = false; + $sameNamespace = false; + if (\strpos($fullName, '\\', 1) === false) { + $globalNamespace = true; + $errorCode = 'FromGlobalNamespace'; + $data[0] = ' from the global namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace'); + } elseif ($this->currentNamespace !== '' + && (\stripos($fullName, $this->currentNamespace . '\\') === 0 + || \stripos($fullName, '\\' . $this->currentNamespace . '\\') === 0) + ) { + $sameNamespace = true; + $errorCode = 'FromSameNamespace'; + $data[0] = ' from the same namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace'); + } + + $hasAlias = false; + $lastLeaf = \strtolower(\substr($fullName, -(\strlen($alias) + 1))); + $aliasLC = \strtolower($alias); + if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) { + $hasAlias = true; + $error .= ' with alias: "%s"'; + $errorCode .= 'WithAlias'; + $data[] = $alias; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias'); + } + + if ($errorCode === 'Found') { + $errorCode = 'FoundWithoutAlias'; + } + + $phpcsFile->addError($error, $reportPtr, $errorCode, $data); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php new file mode 100644 index 00000000..46a39edb --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php @@ -0,0 +1,211 @@ + + */ + public function register() + { + return [ + \T_USE, + \T_NAMESPACE, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $file = $phpcsFile->getFilename(); + if ($file !== $this->currentFile) { + // Reset the current namespace for each new file. + $this->currentFile = $file; + $this->currentNamespace = ''; + } + + $tokens = $phpcsFile->getTokens(); + + // Get the name of the current namespace. + if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) { + $namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr); + if ($namespaceName !== false) { + $this->currentNamespace = $namespaceName; + } + + return; + } + + // Ok, so this is a T_USE token. + try { + $statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); + } catch (RuntimeException $e) { + // Not an import use statement. Bow out. + return; + } + + if (empty($statements['function'])) { + // No import statements for functions found. + return; + } + + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['function'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; // @codeCoverageIgnore + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching. + continue; + } + + break; + } while (true); + + /* + * Build the error message and code. + * + * Check whether this is a non-namespaced (global) import and check whether this is an + * import from within the same namespace. + * + * Takes incorrect use statements with leading backslash into account. + * Takes case-INsensitivity of namespaces names into account. + * + * The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code + * in case this is a non-namespaced file. + */ + + $error = 'Use import statements for functions%s are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $errorCode = 'Found'; + $data = [ + '', + $fullName, + ]; + + $globalNamespace = false; + $sameNamespace = false; + if (\strpos($fullName, '\\', 1) === false) { + $globalNamespace = true; + $errorCode = 'FromGlobalNamespace'; + $data[0] = ' from the global namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace'); + } elseif ($this->currentNamespace !== '' + && (\stripos($fullName, $this->currentNamespace . '\\') === 0 + || \stripos($fullName, '\\' . $this->currentNamespace . '\\') === 0) + ) { + $sameNamespace = true; + $errorCode = 'FromSameNamespace'; + $data[0] = ' from the same namespace'; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace'); + } + + $hasAlias = false; + $lastLeaf = \strtolower(\substr($fullName, -(\strlen($alias) + 1))); + $aliasLC = \strtolower($alias); + if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) { + $hasAlias = true; + $error .= ' with alias: "%s"'; + $errorCode .= 'WithAlias'; + $data[] = $alias; + + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias'); + } else { + $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias'); + } + + if ($errorCode === 'Found') { + $errorCode = 'FoundWithoutAlias'; + } + + $phpcsFile->addError($error, $reportPtr, $errorCode, $data); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/KeywordSpacingSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/KeywordSpacingSniff.php new file mode 100644 index 00000000..3dd47c03 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/KeywordSpacingSniff.php @@ -0,0 +1,207 @@ + + */ + protected $keywords = [ + 'const' => true, + 'function' => true, + ]; + + /** + * Returns an array of tokens this sniff wants to listen for. + * + * @since 1.1.0 + * + * @return array + */ + public function register() + { + return [\T_USE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { + // Trait or closure use statement. + return; + } + + $tokens = $phpcsFile->getTokens(); + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + if ($endOfStatement === false) { + // Live coding or parse error. + return; + } + + // Check the spacing after the `use` keyword. + $this->checkSpacingAfterKeyword($phpcsFile, $stackPtr, $tokens[$stackPtr]['content']); + + // Check the spacing before and after each `as` keyword. + $current = $stackPtr; + do { + $current = $phpcsFile->findNext(\T_AS, ($current + 1), $endOfStatement); + if ($current === false) { + break; + } + + // Prevent false positives when "as" is used within a "name". + if (isset(Tokens::$emptyTokens[$tokens[($current - 1)]['code']]) === true) { + $this->checkSpacingBeforeKeyword($phpcsFile, $current, $tokens[$current]['content']); + $this->checkSpacingAfterKeyword($phpcsFile, $current, $tokens[$current]['content']); + } + } while (true); + + /* + * Check the spacing after `function` and `const` keywords. + */ + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if (isset($this->keywords[\strtolower($tokens[$nextNonEmpty]['content'])]) === true) { + // Keyword found at start of statement, applies to whole statement. + $this->checkSpacingAfterKeyword($phpcsFile, $nextNonEmpty, $tokens[$nextNonEmpty]['content']); + return; + } + + // This may still be a group use statement with function/const substatements. + $openGroup = $phpcsFile->findNext(\T_OPEN_USE_GROUP, ($stackPtr + 1), $endOfStatement); + if ($openGroup === false) { + // Not a group use statement. + return; + } + + $closeGroup = $phpcsFile->findNext(\T_CLOSE_USE_GROUP, ($openGroup + 1), $endOfStatement); + + $current = $openGroup; + do { + $current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $closeGroup, true); + if ($current === false) { + return; + } + + if (isset($this->keywords[\strtolower($tokens[$current]['content'])]) === true) { + $this->checkSpacingAfterKeyword($phpcsFile, $current, $tokens[$current]['content']); + } + + // We're within the use group, so find the next comma. + $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $closeGroup); + } while ($current !== false); + } + + /** + * Check the spacing before a found keyword. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the keyword in the token stack. + * @param string $content The keyword as found. + * + * @return void + */ + public function checkSpacingBeforeKeyword(File $phpcsFile, $stackPtr, $content) + { + $contentLC = \strtolower($content); + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $prevNonEmpty, + 1, // Expected spaces. + 'Expected %s before the "' . $contentLC . '" keyword. Found: %s', + 'SpaceBefore' . \ucfirst($contentLC), + 'error', + 0, // Severity. + \sprintf(self::METRIC_NAME_BEFORE, $contentLC) + ); + } + + /** + * Check the spacing after a found keyword. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the keyword in the token stack. + * @param string $content The keyword as found. + * + * @return void + */ + public function checkSpacingAfterKeyword(File $phpcsFile, $stackPtr, $content) + { + $contentLC = \strtolower($content); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $nextNonEmpty, + 1, // Expected spaces. + 'Expected %s after the "' . $contentLC . '" keyword. Found: %s', + 'SpaceAfter' . \ucfirst($contentLC), + 'error', + 0, // Severity. + \sprintf(self::METRIC_NAME_AFTER, $contentLC) + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/LowercaseFunctionConstSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/LowercaseFunctionConstSniff.php new file mode 100644 index 00000000..b9f87f24 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/LowercaseFunctionConstSniff.php @@ -0,0 +1,156 @@ + + */ + protected $keywords = [ + 'const' => true, + 'function' => true, + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return [\T_USE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { + // Trait or closure use statement. + return; + } + + $tokens = $phpcsFile->getTokens(); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false) { + // Live coding or parse error. + return; + } + + if (isset($this->keywords[\strtolower($tokens[$nextNonEmpty]['content'])]) === true) { + // Keyword found at start of statement, applies to whole statement. + $this->processKeyword($phpcsFile, $nextNonEmpty, $tokens[$nextNonEmpty]['content']); + return; + } + + // This may still be a group use statement with function/const substatements. + $openGroup = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_USE_GROUP], ($stackPtr + 1)); + if ($openGroup === false || $tokens[$openGroup]['code'] !== \T_OPEN_USE_GROUP) { + // Not a group use statement. + return; + } + + $closeGroup = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_CLOSE_USE_GROUP], ($openGroup + 1)); + if ($closeGroup === false || $tokens[$closeGroup]['code'] !== \T_CLOSE_USE_GROUP) { + // Live coding or parse error. + return; + } + + $current = $openGroup; + do { + $current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $closeGroup, true); + if ($current === false) { + return; + } + + if (isset($this->keywords[\strtolower($tokens[$current]['content'])]) === true) { + $this->processKeyword($phpcsFile, $current, $tokens[$current]['content']); + } + + // We're within the use group, so find the next comma. + $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $closeGroup); + } while ($current !== false); + } + + /** + * Processes a found keyword. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the keyword in the token stack. + * @param string $content The keyword as found. + * + * @return void + */ + public function processKeyword(File $phpcsFile, $stackPtr, $content) + { + $contentLC = \strtolower($content); + $metricName = \sprintf(self::METRIC_NAME, $contentLC); + if ($contentLC === $content) { + // Already lowercase. Bow out. + $phpcsFile->recordMetric($stackPtr, $metricName, 'lowercase'); + return; + } + + if (\strtoupper($content) === $content) { + $phpcsFile->recordMetric($stackPtr, $metricName, 'uppercase'); + } else { + $phpcsFile->recordMetric($stackPtr, $metricName, 'mixed case'); + } + + $error = 'The "%s" keyword when used in an import use statements must be lowercase.'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotLowercase', [$contentLC]); + + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, $contentLC); + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoLeadingBackslashSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoLeadingBackslashSniff.php new file mode 100644 index 00000000..1fccc828 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoLeadingBackslashSniff.php @@ -0,0 +1,170 @@ + + */ + public function register() + { + return [\T_USE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { + // Trait or closure use statement. + return; + } + + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_USE_GROUP], ($stackPtr + 1)); + if ($endOfStatement === false) { + // Live coding or parse error. + return; + } + + $tokens = $phpcsFile->getTokens(); + $current = $stackPtr; + + do { + $continue = $this->processImport($phpcsFile, $current, $endOfStatement); + if ($continue === false) { + break; + } + + // Move the stackPtr forward to the next part of the use statement, if any. + $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement); + } while ($current !== false); + + if ($tokens[$endOfStatement]['code'] !== \T_OPEN_USE_GROUP) { + // Finished the statement. + return; + } + + $current = $endOfStatement; // Group open brace. + $endOfStatement = $phpcsFile->findNext([\T_CLOSE_USE_GROUP], ($endOfStatement + 1)); + if ($endOfStatement === false) { + // Live coding or parse error. + return; + } + + do { + $continue = $this->processImport($phpcsFile, $current, $endOfStatement, true); + if ($continue === false) { + break; + } + + // Move the stackPtr forward to the next part of the use statement, if any. + $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement); + } while ($current !== false); + } + + /** + * Examine an individual import statement. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token. + * @param int $endOfStatement End token for the current import statement. + * @param bool $groupUse Whether the current statement is a partial one + * within a group use statement. + * + * @return bool Whether or not to continue examining this import use statement. + */ + private function processImport(File $phpcsFile, $stackPtr, $endOfStatement, $groupUse = false) + { + $tokens = $phpcsFile->getTokens(); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $endOfStatement, true); + if ($nextNonEmpty === false) { + // Reached the end of the statement. + return false; + } + + // Skip past 'function'/'const' keyword. + $contentLC = \strtolower($tokens[$nextNonEmpty]['content']); + if ($tokens[$nextNonEmpty]['code'] === \T_STRING + && ($contentLC === 'function' || $contentLC === 'const') + ) { + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $endOfStatement, true); + if ($nextNonEmpty === false) { + // Reached the end of the statement. + return false; + } + } + + if ($tokens[$nextNonEmpty]['code'] === \T_NS_SEPARATOR) { + $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'yes'); + + $error = 'An import use statement should never start with a leading backslash'; + $code = 'LeadingBackslashFound'; + + if ($groupUse === true) { + $error = 'Parse error: partial import use statement in a use group starting with a leading backslash'; + $code = 'LeadingBackslashFoundInGroup'; + } + + $fix = $phpcsFile->addFixableError($error, $nextNonEmpty, $code); + + if ($fix === true) { + if ($tokens[$nextNonEmpty - 1]['code'] !== \T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($nextNonEmpty, ' '); + } else { + $phpcsFile->fixer->replaceToken($nextNonEmpty, ''); + } + } + } else { + $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'no'); + } + + return true; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php new file mode 100644 index 00000000..6d699934 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php @@ -0,0 +1,164 @@ + + */ + public function register() + { + return [\T_USE]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { + // Closure or trait use statement. Bow out. + return; + } + + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + if ($endOfStatement === false) { + // Parse error or live coding. + return; + } + + $hasAliases = $phpcsFile->findNext(\T_AS, ($stackPtr + 1), $endOfStatement); + if ($hasAliases === false) { + // This use import statement does not alias anything, bow out. + return; + } + + $useStatements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); + if (\count($useStatements, \COUNT_RECURSIVE) <= 3) { + // No statements found. Shouldn't be possible, but still. Bow out. + return; + } + + $tokens = $phpcsFile->getTokens(); + + // Collect all places where aliases are used in this use statement. + $aliasPtrs = []; + $currentAs = $hasAliases; + do { + $aliasPtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($currentAs + 1), null, true); + if ($aliasPtr !== false && $tokens[$aliasPtr]['code'] === \T_STRING) { + $aliasPtrs[$currentAs] = $aliasPtr; + } + + $currentAs = $phpcsFile->findNext(\T_AS, ($currentAs + 1), $endOfStatement); + } while ($currentAs !== false); + + // Now check the names in each use statement for useless aliases. + foreach ($useStatements as $type => $statements) { + foreach ($statements as $alias => $fqName) { + $unqualifiedName = \ltrim(\substr($fqName, \strrpos($fqName, '\\')), '\\'); + + $uselessAlias = false; + if ($type === 'const') { + // Do a case-sensitive comparison for constants. + if ($unqualifiedName === $alias) { + $uselessAlias = true; + } + } elseif (NamingConventions::isEqual($unqualifiedName, $alias)) { + $uselessAlias = true; + } + + if ($uselessAlias === false) { + continue; + } + + // Now check if this is actually used as an alias or just the actual name. + foreach ($aliasPtrs as $asPtr => $aliasPtr) { + if ($tokens[$aliasPtr]['content'] !== $alias) { + continue; + } + + // Make sure this is really the right one. + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($asPtr - 1), null, true); + if ($tokens[$prev]['code'] !== \T_STRING + || $tokens[$prev]['content'] !== $unqualifiedName + ) { + continue; + } + + $error = 'Useless alias "%s" found for import of "%s"'; + $code = 'Found'; + $data = [$alias, $fqName]; + + // Okay, so this is the one which should be flagged. + $hasComments = $phpcsFile->findNext(Tokens::$commentTokens, ($prev + 1), $aliasPtr); + if ($hasComments !== false) { + // Don't auto-fix if there are comments. + $phpcsFile->addError($error, $aliasPtr, $code, $data); + break; + } + + $fix = $phpcsFile->addFixableError($error, $aliasPtr, $code, $data); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + for ($i = ($prev + 1); $i <= $aliasPtr; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + break; + } + } + } + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/AnonClassKeywordSpacingSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/AnonClassKeywordSpacingSniff.php new file mode 100644 index 00000000..31163165 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/AnonClassKeywordSpacingSniff.php @@ -0,0 +1,79 @@ + + */ + public function register() + { + return [\T_ANON_CLASS]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) { + // No parentheses, nothing to do. + return; + } + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $nextNonEmpty, + (int) $this->spacing, + 'There must be %1$s between the class keyword and the open parenthesis for an anonymous class. Found: %2$s', + 'Incorrect', + 'error', + 0, + 'Anon class: space between keyword and open parenthesis' + ); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php new file mode 100644 index 00000000..376e9046 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php @@ -0,0 +1,408 @@ + + */ + public function register() + { + return [\T_COMMA]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + if (isset($this->phpVersion) === false || \defined('PHP_CODESNIFFER_IN_TESTS')) { + // Set default value to prevent this code from running every time the sniff is triggered. + $this->phpVersion = 0; + + $phpVersion = Helper::getConfigData('php_version'); + if ($phpVersion !== null) { + $this->phpVersion = (int) $phpVersion; + } + } + + $this->processSpacingBefore($phpcsFile, $stackPtr); + $this->processSpacingAfter($phpcsFile, $stackPtr); + } + + /** + * Check the spacing before the comma. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + protected function processSpacingBefore(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true); + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + if ($prevNonWhitespace !== $prevNonEmpty + && $tokens[$prevNonEmpty]['code'] !== \T_COMMA + && $tokens[$prevNonEmpty]['line'] !== $tokens[$nextNonEmpty]['line'] + ) { + // Special case: comma after a trailing comment - the comma should be moved to before the comment. + $fix = $phpcsFile->addFixableError( + 'Comma found after comment, expected the comma after the end of the code', + $stackPtr, + 'CommaAfterComment' + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($stackPtr, ''); + $phpcsFile->fixer->addContent($prevNonEmpty, ','); + + // Clean up potential trailing whitespace left behind, but don't remove blank lines. + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[($stackPtr - 1)]['code'] === \T_WHITESPACE + && $tokens[($stackPtr - 1)]['line'] === $tokens[$stackPtr]['line'] + && $tokens[$stackPtr]['line'] !== $tokens[$nextNonWhitespace]['line'] + ) { + $phpcsFile->fixer->replaceToken(($stackPtr - 1), ''); + } + + $phpcsFile->fixer->endChangeset(); + } + return; + } + + if ($tokens[$prevNonWhitespace]['code'] === \T_COMMA) { + // This must be a list assignment with ignored items. Ignore. + return; + } + + if (isset(Tokens::$blockOpeners[$tokens[$prevNonWhitespace]['code']]) === true + || $tokens[$prevNonWhitespace]['code'] === \T_OPEN_SHORT_ARRAY + || $tokens[$prevNonWhitespace]['code'] === \T_OPEN_USE_GROUP + ) { + // Should only realistically be possible for lists. Leave for a block brace spacing sniff to sort out. + return; + } + + $expectedSpaces = 0; + + if ($tokens[$prevNonEmpty]['code'] === \T_END_HEREDOC + || $tokens[$prevNonEmpty]['code'] === \T_END_NOWDOC + ) { + /* + * If php_version is explicitly set to PHP < 7.3, enforce a new line between the closer and the comma. + * + * If php_version is *not* explicitly set, let the indent be leading and only enforce + * a new line between the closer and the comma when this is an old-style heredoc/nowdoc. + */ + if ($this->phpVersion !== 0 && $this->phpVersion < 70300) { + $expectedSpaces = 'newline'; + } + + if ($this->phpVersion === 0 + && \ltrim($tokens[$prevNonEmpty]['content']) === $tokens[$prevNonEmpty]['content'] + ) { + $expectedSpaces = 'newline'; + } + } + + $error = 'Expected %1$s between "' . $this->escapePlaceholders($tokens[$prevNonWhitespace]['content']) + . '" and the comma. Found: %2$s'; + $codeSuffix = $this->getSuffix($phpcsFile, $stackPtr); + $metricSuffix = $this->codeSuffixToMetric($codeSuffix); + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $prevNonWhitespace, + $expectedSpaces, + $error, + 'SpaceBefore' . $codeSuffix, + 'error', + 0, + self::METRIC_NAME_BEFORE . $metricSuffix + ); + } + + /** + * Check the spacing after the comma. + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + protected function processSpacingAfter(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($stackPtr + 1), null, true); + if ($nextNonWhitespace === false) { + // Live coding/parse error. Ignore. + return; + } + + if ($tokens[$nextNonWhitespace]['code'] === \T_COMMA) { + // This must be a list assignment with ignored items. Ignore. + return; + } + + if ($tokens[$nextNonWhitespace]['code'] === \T_CLOSE_CURLY_BRACKET + || $tokens[$nextNonWhitespace]['code'] === \T_CLOSE_SQUARE_BRACKET + || $tokens[$nextNonWhitespace]['code'] === \T_CLOSE_PARENTHESIS + || $tokens[$nextNonWhitespace]['code'] === \T_CLOSE_SHORT_ARRAY + || $tokens[$nextNonWhitespace]['code'] === \T_CLOSE_USE_GROUP + ) { + // Ignore. Leave for a block spacing sniff to sort out. + return; + } + + $nextToken = $tokens[($stackPtr + 1)]; + + $error = 'Expected %1$s between the comma and "' + . $this->escapePlaceholders($tokens[$nextNonWhitespace]['content']) . '". Found: %2$s'; + + $codeSuffix = $this->getSuffix($phpcsFile, $stackPtr); + $metricSuffix = $this->codeSuffixToMetric($codeSuffix); + + if ($nextToken['code'] === \T_WHITESPACE) { + if ($nextToken['content'] === ' ') { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_AFTER . $metricSuffix, '1 space'); + return; + } + + // Note: this check allows for trailing whitespace between the comma and a new line char. + // The trailing whitespace is not the concern of this sniff. + if (\ltrim($nextToken['content'], ' ') === $phpcsFile->eolChar) { + $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_AFTER . $metricSuffix, 'a new line'); + return; + } + + $errorCode = 'TooMuchSpaceAfter' . $codeSuffix; + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if (isset(Tokens::$commentTokens[$tokens[$nextNonWhitespace]['code']]) === true + && ($nextNonEmpty === false || $tokens[$stackPtr]['line'] !== $tokens[$nextNonEmpty]['line']) + ) { + // Separate error code to allow for aligning trailing comments. + $errorCode = 'TooMuchSpaceAfterCommaBeforeTrailingComment'; + } + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $nextNonWhitespace, + 1, + $error, + $errorCode, + 'error', + 0, + self::METRIC_NAME_AFTER . $metricSuffix + ); + return; + } + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $nextNonWhitespace, + 1, + $error, + 'NoSpaceAfter' . $codeSuffix, + 'error', + 0, + self::METRIC_NAME_AFTER . $metricSuffix + ); + } + + /** + * Escape arbitrary token content for *printf() placeholders. + * + * @since 1.1.0 + * + * @param string $text Arbitrary text string. + * + * @return string + */ + private function escapePlaceholders($text) + { + return \preg_replace('`(?:^|[^%])(%)(?:[^%]|$)`', '%%', \trim($text)); + } + + /** + * Retrieve a text string for use as a suffix to an error code. + * + * This allows for modular error codes, which in turn allow for selectively excluding + * error codes. + * + * {@internal Closure use will be parentheses owner in PHPCS 4.x, this code will + * need an update for that in due time.} + * + * @since 1.1.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return string + */ + private function getSuffix($phpcsFile, $stackPtr) + { + $opener = Parentheses::getLastOpener($phpcsFile, $stackPtr); + if ($opener === false) { + return ''; + } + + $tokens = $phpcsFile->getTokens(); + + $owner = Parentheses::getOwner($phpcsFile, $opener); + if ($owner !== false) { + switch ($tokens[$owner]['code']) { + case \T_FUNCTION: + case \T_CLOSURE: + case \T_FN: + return 'InFunctionDeclaration'; + + case \T_DECLARE: + return 'InDeclare'; + + case \T_ANON_CLASS: + case \T_ISSET: + case \T_UNSET: + return 'InFunctionCall'; + + // Long array, long list, isset, unset, empty, exit, eval, control structures. + default: + return ''; + } + } + + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true); + + if (isset(Collections::nameTokens()[$tokens[$prevNonEmpty]['code']]) === true) { + return 'InFunctionCall'; + } + + switch ($tokens[$prevNonEmpty]['code']) { + case \T_USE: + return 'InClosureUse'; + + case \T_VARIABLE: + case \T_SELF: + case \T_STATIC: + case \T_PARENT: + return 'InFunctionCall'; + + default: + return ''; + } + } + + /** + * Transform a suffix for an error code into a suffix for a metric. + * + * @since 1.1.0 + * + * @param string $suffix Error code suffix. + * + * @return string + */ + private function codeSuffixToMetric($suffix) + { + return \strtolower(\preg_replace('`([A-Z])`', ' $1', $suffix)); + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php new file mode 100644 index 00000000..817a44e1 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php @@ -0,0 +1,173 @@ + + * + * + * + * The PHPCS native `Generic.Whitespace.DisallowTabIndent` sniff oversteps its reach and silently + * does mid-line tab to space replacements as well. + * However, the sister-sniff `Generic.Whitespace.DisallowSpaceIndent` leaves mid-line tabs/spaces alone. + * This sniff fills that gap. + * + * @since 1.0.0 + */ +final class DisallowInlineTabsSniff implements Sniff +{ + + /** + * The --tab-width CLI value that is being used. + * + * @since 1.0.0 + * + * @var int + */ + private $tabWidth; + + /** + * Tokens to check for mid-line tabs. + * + * @since 1.0.0 + * + * @var array + */ + private $find = [ + \T_WHITESPACE => true, + \T_DOC_COMMENT_WHITESPACE => true, + \T_DOC_COMMENT_STRING => true, + \T_COMMENT => true, + ]; + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return Collections::phpOpenTags(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return int Integer stack pointer to skip the rest of the file. + */ + public function process(File $phpcsFile, $stackPtr) + { + if (isset($this->tabWidth) === false) { + $this->tabWidth = (int) Helper::getTabWidth($phpcsFile); + } + + if (\defined('PHP_CODESNIFFER_IN_TESTS')) { + $this->tabWidth = (int) Helper::getCommandLineData($phpcsFile, 'tabWidth'); + } + + $tokens = $phpcsFile->getTokens(); + $dummy = new DummyTokenizer('', $phpcsFile->config); + + for ($i = 0; $i < $phpcsFile->numTokens; $i++) { + // Skip all non-whitespace tokens and skip whitespace at the start of a new line. + if (isset($this->find[$tokens[$i]['code']]) === false + || (($tokens[$i]['code'] === \T_WHITESPACE + || $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE) + && $tokens[$i]['column'] === 1) + ) { + continue; + } + + // If tabs haven't been converted to spaces by the tokenizer, do so now. + $token = $tokens[$i]; + if (isset($token['orig_content']) === false) { + if ($token['content'] === '' || \strpos($token['content'], "\t") === false) { + // If there are no tabs, we can continue, no matter what. + continue; + } + + $dummy->replaceTabsInToken($token); + } + + /* + * Tokens only have the 'orig_content' key if they contain tabs, + * so from here on out, we **know** there will be tabs in the content. + */ + $origContent = $token['orig_content']; + $commentOnly = ''; + + $multiLineComment = false; + if (($tokens[$i]['code'] === \T_COMMENT + || isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']])) + && $tokens[$i]['column'] === 1 + && ($tokens[($i - 1)]['code'] === \T_COMMENT + || isset(Tokens::$phpcsCommentTokens[$tokens[($i - 1)]['code']])) + ) { + $multiLineComment = true; + } + + if ($multiLineComment === true) { + // This is the subsequent line of a multi-line comment. Account for indentation. + $commentOnly = \ltrim($origContent); + if ($commentOnly === '' || \strpos($commentOnly, "\t") === false) { + continue; + } + } + + $fix = $phpcsFile->addFixableError( + 'Spaces must be used for mid-line alignment; tabs are not allowed', + $i, + 'NonIndentTabsUsed' + ); + + if ($fix === false) { + continue; + } + + $indent = ''; + if ($multiLineComment === true) { + // Take the original indent (tabs/spaces) and combine with the tab-replaced comment content. + $indent = \str_replace($commentOnly, '', $origContent); + $token['content'] = \ltrim($token['content']); + } + + $phpcsFile->fixer->replaceToken($i, $indent . $token['content']); + } + + // Scanned the whole file in one go. Don't scan this file again. + return $phpcsFile->numTokens; + } +} diff --git a/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php new file mode 100644 index 00000000..af689684 --- /dev/null +++ b/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php @@ -0,0 +1,445 @@ + + * + * + * + * + * + * + * + * + * + * ``` + * + * @since 1.0.0 + * + * @var string[] + */ + public $ignoreAlignmentBefore = []; + + /** + * Whether or not potential trailing whitespace on otherwise blank lines should be examined or ignored. + * + * Defaults to `true`, i.e. ignore blank lines. + * + * It is recommended to only set this to `false` if the standard including this sniff does not + * include the `Squiz.WhiteSpace.SuperfluousWhitespace` sniff (which is included in most standards). + * + * @since 1.0.0 + * + * @var bool + */ + public $ignoreBlankLines = true; + + /** + * The --tab-width CLI value that is being used. + * + * @since 1.0.0 + * + * @var int + */ + private $tabWidth; + + /** + * Whitespace tokens and tokens which can contain leading whitespace. + * + * A few additional tokens will be added to this list in the register() method. + * + * @since 1.0.0 + * + * @var array + */ + private $tokensToCheck = [ + \T_WHITESPACE => \T_WHITESPACE, + \T_INLINE_HTML => \T_INLINE_HTML, + \T_DOC_COMMENT_WHITESPACE => \T_DOC_COMMENT_WHITESPACE, + \T_COMMENT => \T_COMMENT, + \T_END_HEREDOC => \T_END_HEREDOC, + \T_END_NOWDOC => \T_END_NOWDOC, + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + // Add the ignore annotation tokens to the list of tokens to check. + $this->tokensToCheck += Tokens::$phpcsCommentTokens; + + return Collections::phpOpenTags(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return int Integer stack pointer to skip the rest of the file. + */ + public function process(File $phpcsFile, $stackPtr) + { + /* + * Handle the properties. + */ + if (isset($this->tabWidth) === false || \defined('PHP_CODESNIFFER_IN_TESTS') === true) { + $this->tabWidth = Helper::getTabWidth($phpcsFile); + } + + if (isset($this->indent) === true) { + $indent = (int) $this->indent; + } else { + $indent = $this->tabWidth; + } + + $ignoreTokens = (array) $this->ignoreAlignmentBefore; + if (empty($ignoreTokens) === false) { + $ignoreTokens = \array_flip($ignoreTokens); + } + + /* + * Check the whole file in one go. + */ + $tokens = $phpcsFile->getTokens(); + + for ($i = 0; $i < $phpcsFile->numTokens; $i++) { + if ($tokens[$i]['column'] !== 1) { + // Only interested in the first token on each line. + continue; + } + + if (isset($this->tokensToCheck[$tokens[$i]['code']]) === false) { + // Not one of the target tokens. + continue; + } + + if ($tokens[$i]['content'] === $phpcsFile->eolChar) { + // Skip completely blank lines. + continue; + } + + if (isset($ignoreTokens[$tokens[$i]['type']]) === true + || (isset($tokens[($i + 1)]) && isset($ignoreTokens[$tokens[($i + 1)]['type']])) + ) { + // This is one of the tokens being ignored. + continue; + } + + $origContent = null; + if (isset($tokens[$i]['orig_content']) === true) { + $origContent = $tokens[$i]['orig_content']; + } + + $spaces = 0; + $length = 0; + $content = ''; + $closer = ''; + + switch ($tokens[$i]['code']) { + case \T_WHITESPACE: + if ($this->ignoreBlankLines === true + && isset($tokens[($i + 1)]) + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { + // Skip blank lines which only contain trailing whitespace. + continue 2; + } + + $spaces = ($tokens[$i]['length'] % $indent); + break; + + case \T_DOC_COMMENT_WHITESPACE: + /* + * Blank lines with trailing whitespace in docblocks are tokenized as + * two T_DOC_COMMENT_WHITESPACE tokens: one for the trailing whitespace, + * one for the new line character. + */ + if ($this->ignoreBlankLines === true + && isset($tokens[($i + 1)]) + && $tokens[($i + 1)]['content'] === $phpcsFile->eolChar + && isset($tokens[($i + 2)]) + && $tokens[$i]['line'] !== $tokens[($i + 2)]['line'] + ) { + // Skip blank lines which only contain trailing whitespace. + continue 2; + } + + $spaces = ($tokens[$i]['length'] % $indent); + + if (isset($tokens[($i + 1)]) === true + && ($tokens[($i + 1)]['code'] === \T_DOC_COMMENT_STAR + || $tokens[($i + 1)]['code'] === \T_DOC_COMMENT_CLOSE_TAG) + && $spaces !== 0 + ) { + // One alignment space expected before the *. + --$spaces; + } + break; + + case \T_COMMENT: + case \T_INLINE_HTML: + if ($this->ignoreBlankLines === true + && \trim($tokens[$i]['content']) === '' + && isset($tokens[($i + 1)]) + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { + // Skip blank lines which only contain trailing whitespace. + continue 2; + } + + // Deliberate fall-through. + + case \T_PHPCS_ENABLE: + case \T_PHPCS_DISABLE: + case \T_PHPCS_SET: + case \T_PHPCS_IGNORE: + case \T_PHPCS_IGNORE_FILE: + /* + * Indentation is included in the contents of the token for: + * - inline HTML + * - PHP 7.3+ flexible heredoc/nowdoc closer identifiers (see below); + * - subsequent lines of multi-line comments; + * - PHPCS native annotations when part of a multi-line comment. + */ + $content = \ltrim($tokens[$i]['content']); + $whitespace = \str_replace($content, '', $tokens[$i]['content']); + + /* + * If there is no content, this is a blank line in a comment or in inline HTML. + * In that case, use the predetermined length as otherwise the new line character + * at the end of the whitespace will throw the count off. + */ + $length = ($content === '') ? $tokens[$i]['length'] : \strlen($whitespace); + $spaces = ($length % $indent); + + /* + * For multi-line star-comments, which use (aligned) stars on subsequent + * lines, we don't want to trigger on the one extra space before the star. + * + * While not 100% correct, don't exclude inline HTML from this check as + * otherwise the sniff would trigger on multi-line /*-style inline javascript comments. + * This may cause false negatives as there is no check for being in a + * ', + esc_url( $path_to_file ) +); + ]]> + + + + + + + + wp_enqueue_style( + 'style-name', + $path_to_file, + array(), + '1.0.0' +); + ]]> + + + ', + esc_url( $path_to_file ) +); + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Docs/WP/PostsPerPageStandard.xml b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WP/PostsPerPageStandard.xml new file mode 100644 index 00000000..55715ca8 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WP/PostsPerPageStandard.xml @@ -0,0 +1,73 @@ + + + + + + + + -1, +); +$args = array( + 'posts_per_page' => 100, +); +$args = array( + 'posts_per_page' => '10', +); + +$query_args['posts_per_page'] = 100; + +_query_posts( 'nopaging=1&posts_per_page=50' ); + ]]> + + + 101, +); + +$query_args['posts_per_page'] = 200; + +_query_posts( 'nopaging=1&posts_per_page=999' ); + ]]> + + + + + -1, +); +$args = array( + 'numberposts' => 100, +); +$args = array( + 'numberposts' => '10', +); + +$query_args['numberposts'] = '-1'; + +_query_posts( 'numberposts=50' ); + ]]> + + + 101, +); + +$query_args['numberposts'] = '200'; + +_query_posts( 'numberposts=999' ); + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/CastStructureSpacingStandard.xml b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/CastStructureSpacingStandard.xml new file mode 100644 index 00000000..a087dd54 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/CastStructureSpacingStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + (int) '420'; + +// No space between spread operator and cast. +$a = function_call( ...(array) $mixed ); + ]]> + + + =(int) '420'; + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ControlStructureSpacingStandard.xml b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ControlStructureSpacingStandard.xml new file mode 100644 index 00000000..943967a6 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ControlStructureSpacingStandard.xml @@ -0,0 +1,150 @@ + + + + + + + + ( have_posts() ) {} + +// For multi-line conditions, +// a new line is also accepted. +if ( true === $condition + && $count > 10 +) {} + ]]> + + + (have_posts()){} + +// Too much space. +while ( have_posts() ) {} + ]]> + + + + + + + + { + // Do something. +} catch ( + ExceptionA | ExceptionB $e +) { +} + ]]> + + + { + // Do something. +} catch ( Exception $e ) +( +} + ]]> + + + + + { + // Do something. +} + ]]> + + + { + // Do something. +} + ]]> + + + + + + + + : + // Do something. +endforeach; + ]]> + + + : + // Do something. +endforeach; + ]]> + + + + + + + + + + + + + +} + ]]> + + + + + + + + + + + + + echo $a; + + +} + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ObjectOperatorSpacingStandard.xml b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ObjectOperatorSpacingStandard.xml new file mode 100644 index 00000000..47ffb1e8 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/ObjectOperatorSpacingStandard.xml @@ -0,0 +1,19 @@ + + + , ?->, ::) should not have any spaces around them, though new lines are allowed except for use with the `::class` constant. + ]]> + + + + ->bar(); + ]]> + + + ?-> bar(); + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/OperatorSpacingStandard.xml b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/OperatorSpacingStandard.xml new file mode 100644 index 00000000..f9e0719f --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Docs/WhiteSpace/OperatorSpacingStandard.xml @@ -0,0 +1,61 @@ + + + + + + + + === $b && $b === $c ) {} +if ( ! $var ) {} + ]]> + + + && $b === $c ) {} +if ( ! $var ) {} + +// Too little space. +if ( $a===$b &&$b ===$c ) {} +if ( !$var ) {} + ]]> + + + + + + && $b === $c +) {} + ]]> + + + + && $b === $c +) {} + ]]> + + + + + = 'foo'; +$all = 'foobar'; + ]]> + + + = 'foo'; +$all ='foobar'; + ]]> + + + diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ArrayWalkingFunctionsHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ArrayWalkingFunctionsHelper.php new file mode 100644 index 00000000..76623d7d --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ArrayWalkingFunctionsHelper.php @@ -0,0 +1,108 @@ + + */ + private static $arrayWalkingFunctions = array( + 'array_map' => array( + 'position' => 1, + 'name' => 'callback', + ), + 'map_deep' => array( + 'position' => 2, + 'name' => 'callback', + ), + ); + + /** + * Retrieve a list of the supported "array walking" functions. + * + * @since 3.0.0 + * + * @return array + */ + public static function get_functions() { + return \array_fill_keys( \array_keys( self::$arrayWalkingFunctions ), true ); + } + + /** + * Check if a particular function is an "array walking" function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + public static function is_array_walking_function( $functionName ) { + return isset( self::$arrayWalkingFunctions[ strtolower( $functionName ) ] ); + } + + /** + * Retrieve the parameter information for the callback parameter for an array walking function. + * + * @since 3.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of function call name token. + * + * @return array|false Array with information on the callback parameter. + * Or `FALSE` if the parameter is not found. + * See the PHPCSUtils PassedParameters::getParameters() documentation + * for the format of the returned (single-dimensional) array. + */ + public static function get_callback_parameter( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $functionName = strtolower( $tokens[ $stackPtr ]['content'] ); + if ( isset( self::$arrayWalkingFunctions[ $functionName ] ) === false ) { + return false; + } + + return PassedParameters::getParameter( + $phpcsFile, + $stackPtr, + self::$arrayWalkingFunctions[ $functionName ]['position'], + self::$arrayWalkingFunctions[ $functionName ]['name'] + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ConstantsHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ConstantsHelper.php new file mode 100644 index 00000000..080f3cf2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ConstantsHelper.php @@ -0,0 +1,135 @@ +getTokens(); + + // Check for the existence of the token. + if ( ! isset( $tokens[ $stackPtr ] ) ) { + return false; + } + + // Is this one of the tokens this function handles ? + if ( \T_STRING !== $tokens[ $stackPtr ]['code'] ) { + return false; + } + + $next = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false !== $next + && ( \T_OPEN_PARENTHESIS === $tokens[ $next ]['code'] + || \T_DOUBLE_COLON === $tokens[ $next ]['code'] ) + ) { + // Function call or declaration. + return false; + } + + // Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant. + $tokens_to_ignore = array( + \T_NAMESPACE => true, + \T_USE => true, + \T_EXTENDS => true, + \T_IMPLEMENTS => true, + \T_NEW => true, + \T_FUNCTION => true, + \T_INSTANCEOF => true, + \T_INSTEADOF => true, + \T_GOTO => true, + ); + $tokens_to_ignore += Tokens::$ooScopeTokens; + $tokens_to_ignore += Collections::objectOperators(); + $tokens_to_ignore += Tokens::$scopeModifiers; + + $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( isset( $tokens_to_ignore[ $tokens[ $prev ]['code'] ] ) ) { + // Not the use of a constant. + return false; + } + + if ( ContextHelper::is_token_namespaced( $phpcsFile, $stackPtr ) === true ) { + // Namespaced constant of the same name. + return false; + } + + if ( \T_CONST === $tokens[ $prev ]['code'] + && Scopes::isOOConstant( $phpcsFile, $prev ) + ) { + // Class constant declaration of the same name. + return false; + } + + /* + * Deal with a number of variations of use statements. + */ + for ( $i = $stackPtr; $i > 0; $i-- ) { + if ( $tokens[ $i ]['line'] !== $tokens[ $stackPtr ]['line'] ) { + break; + } + } + + $firstOnLine = $phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( false !== $firstOnLine && \T_USE === $tokens[ $firstOnLine ]['code'] ) { + $nextOnLine = $phpcsFile->findNext( Tokens::$emptyTokens, ( $firstOnLine + 1 ), null, true ); + if ( false !== $nextOnLine ) { + if ( \T_STRING === $tokens[ $nextOnLine ]['code'] + && 'const' === $tokens[ $nextOnLine ]['content'] + ) { + $hasNsSep = $phpcsFile->findNext( \T_NS_SEPARATOR, ( $nextOnLine + 1 ), $stackPtr ); + if ( false !== $hasNsSep ) { + // Namespaced const (group) use statement. + return false; + } + } else { + // Not a const use statement. + return false; + } + } + } + + return true; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ContextHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ContextHelper.php new file mode 100644 index 00000000..8b5c1786 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ContextHelper.php @@ -0,0 +1,394 @@ + Key is token constant, value irrelevant. + */ + private static $safe_casts = array( + \T_INT_CAST => true, + \T_DOUBLE_CAST => true, + \T_BOOL_CAST => true, + \T_UNSET_CAST => true, + ); + + /** + * List of PHP native functions to test the type of a variable. + * + * Using these functions is safe in combination with superglobals without + * unslashing or sanitization. + * + * They should, however, not be regarded as unslashing or sanitization functions. + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The property visibility was changed from `protected` to `private static`. + * + * @var array Key is function name, value irrelevant. + */ + private static $typeTestFunctions = array( + 'is_array' => true, + 'is_bool' => true, + 'is_callable' => true, + 'is_countable' => true, + 'is_double' => true, + 'is_float' => true, + 'is_int' => true, + 'is_integer' => true, + 'is_iterable' => true, + 'is_long' => true, + 'is_null' => true, + 'is_numeric' => true, + 'is_object' => true, + 'is_real' => true, + 'is_resource' => true, + 'is_scalar' => true, + 'is_string' => true, + ); + + /** + * List of PHP native functions to check if an array index exists. + * + * @since 3.0.0 + * + * @var array Key is function name, value irrelevant. + */ + private static $key_exists_functions = array( + 'array_key_exists' => true, + 'key_exists' => true, // Alias. + ); + + /** + * Array functions to compare a $needle to a predefined set of values. + * + * If the value is set to an array, the parameter specified in the array is + * required for the function call to be considered as a comparison. + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The property visibility was changed from `protected` to `private static`. + * + * @var array + */ + private static $arrayCompareFunctions = array( + 'in_array' => true, + 'array_search' => true, + 'array_keys' => array( + 'position' => 2, + 'name' => 'filter_value', + ), + ); + + /** + * Check if a particular token acts - statically or non-statically - on an object. + * + * {@internal Note: this may still mistake a namespaced function imported via a `use` statement for + * a global function!} + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method was renamed from `is_class_object_call() to `has_object_operator_before()`. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool + */ + public static function has_object_operator_before( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $before = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + + return isset( Collections::objectOperators()[ $tokens[ $before ]['code'] ] ); + } + + /** + * Check if a particular token is prefixed with a namespace. + * + * {@internal This will give a false positive if the file is not namespaced and the token is prefixed + * with `namespace\`.} + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool + */ + public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + + if ( \T_NS_SEPARATOR !== $tokens[ $prev ]['code'] ) { + return false; + } + + $before_prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true ); + if ( \T_STRING !== $tokens[ $before_prev ]['code'] + && \T_NAMESPACE !== $tokens[ $before_prev ]['code'] + ) { + return false; + } + + return true; + } + + /** + * Check if a token is (part of) a parameter for a function call to a select list of functions. + * + * This is useful, for instance, when trying to determine the context a variable is used in. + * + * For example: this function could be used to determine if the variable `$foo` is used + * in a global function call to the function `is_foo()`. + * In that case, a call to this function would return the stackPtr to the T_STRING `is_foo` + * for code like: `is_foo( $foo, 'some_other_param' )`, while it would return `false` for + * the following code `is_bar( $foo, 'some_other_param' )`. + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * - The `$global` parameter was renamed to `$global_function`. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * @param array $valid_functions List of valid function names. + * Note: The keys to this array should be the function names + * in lowercase. Values are irrelevant. + * @param bool $global_function Optional. Whether to make sure that the function call is + * to a global function. If `false`, calls to methods, be it static + * `Class::method()` or via an object `$obj->method()`, and + * namespaced function calls, like `MyNS\function_name()` will + * also be accepted. + * Defaults to `true`. + * @param bool $allow_nested Optional. Whether to allow for nested function calls within the + * call to this function. + * I.e. when checking whether a token is within a function call + * to `strtolower()`, whether to accept `strtolower( trim( $var ) )` + * or only `strtolower( $var )`. + * Defaults to `false`. + * + * @return int|bool Stack pointer to the function call T_STRING token or false otherwise. + */ + public static function is_in_function_call( File $phpcsFile, $stackPtr, array $valid_functions, $global_function = true, $allow_nested = false ) { + $tokens = $phpcsFile->getTokens(); + if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { + return false; + } + + $nested_parenthesis = $tokens[ $stackPtr ]['nested_parenthesis']; + if ( false === $allow_nested ) { + $nested_parenthesis = array_reverse( $nested_parenthesis, true ); + } + + foreach ( $nested_parenthesis as $open => $close ) { + $prev_non_empty = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $open - 1 ), null, true ); + if ( false === $prev_non_empty || \T_STRING !== $tokens[ $prev_non_empty ]['code'] ) { + continue; + } + + if ( isset( $valid_functions[ strtolower( $tokens[ $prev_non_empty ]['content'] ) ] ) === false ) { + if ( false === $allow_nested ) { + // Function call encountered, but not to one of the allowed functions. + return false; + } + + continue; + } + + if ( false === $global_function ) { + return $prev_non_empty; + } + + /* + * Now, make sure it is a global function. + */ + if ( self::has_object_operator_before( $phpcsFile, $prev_non_empty ) === true ) { + continue; + } + + if ( self::is_token_namespaced( $phpcsFile, $prev_non_empty ) === true ) { + continue; + } + + return $prev_non_empty; + } + + return false; + } + + /** + * Check if a token is inside of an is_...() statement. + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool Whether the token is being type tested. + */ + public static function is_in_type_test( File $phpcsFile, $stackPtr ) { + /* + * Casting the potential integer stack pointer return value to boolean here is fine. + * The return can never be `0` as there will always be a PHP open tag before the + * function call. + */ + return (bool) self::is_in_function_call( $phpcsFile, $stackPtr, self::$typeTestFunctions ); + } + + /** + * Check if a token is inside of an isset(), empty() or array_key_exists() statement. + * + * @since 0.5.0 + * @since 2.1.0 Now checks for the token being used as the array parameter + * in function calls to array_key_exists() and key_exists() as well. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool Whether the token is inside an isset() or empty() statement. + */ + public static function is_in_isset_or_empty( File $phpcsFile, $stackPtr ) { + if ( Parentheses::lastOwnerIn( $phpcsFile, $stackPtr, array( \T_ISSET, \T_EMPTY ) ) !== false ) { + return true; + } + + $functionPtr = self::is_in_function_call( $phpcsFile, $stackPtr, self::$key_exists_functions ); + if ( false !== $functionPtr ) { + /* + * Both functions being checked have the same parameters. If the function list would + * be expanded, this needs to be revisited. + */ + $array_param = PassedParameters::getParameter( $phpcsFile, $functionPtr, 2, 'array' ); + if ( false !== $array_param + && ( $stackPtr >= $array_param['start'] && $stackPtr <= $array_param['end'] ) + ) { + return true; + } + } + + return false; + } + + /** + * Retrieve a list of the tokens which are regarded as "safe casts". + * + * @since 3.0.0 + * + * @return array + */ + public static function get_safe_cast_tokens() { + return self::$safe_casts; + } + + /** + * Check if something is being casted to a safe value. + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool Whether the token being casted. + */ + public static function is_safe_casted( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + + return isset( self::$safe_casts[ $tokens[ $prev ]['code'] ] ); + } + + /** + * Check if a token is inside of an array-value comparison function. + * + * @since 2.1.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool Whether the token is (part of) a parameter to an + * array-value comparison function. + */ + public static function is_in_array_comparison( File $phpcsFile, $stackPtr ) { + $function_ptr = self::is_in_function_call( $phpcsFile, $stackPtr, self::$arrayCompareFunctions, true, true ); + if ( false === $function_ptr ) { + return false; + } + + $tokens = $phpcsFile->getTokens(); + $function_name = strtolower( $tokens[ $function_ptr ]['content'] ); + if ( true === self::$arrayCompareFunctions[ $function_name ] ) { + return true; + } + + $target_param = self::$arrayCompareFunctions[ $function_name ]; + $found_param = PassedParameters::getParameter( $phpcsFile, $function_ptr, $target_param['position'], $target_param['name'] ); + if ( false !== $found_param ) { + return true; + } + + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/DeprecationHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/DeprecationHelper.php new file mode 100644 index 00000000..13273496 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/DeprecationHelper.php @@ -0,0 +1,84 @@ +getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $ignore = Tokens::$methodPrefixes; + $ignore[ \T_WHITESPACE ] = \T_WHITESPACE; + + for ( $comment_end = ( $stackPtr - 1 ); $comment_end >= 0; $comment_end-- ) { + if ( isset( $ignore[ $tokens[ $comment_end ]['code'] ] ) === true ) { + continue; + } + + if ( \T_ATTRIBUTE_END === $tokens[ $comment_end ]['code'] + && isset( $tokens[ $comment_end ]['attribute_opener'] ) === true + ) { + $comment_end = $tokens[ $comment_end ]['attribute_opener']; + continue; + } + + break; + } + + if ( \T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $comment_end ]['code'] ) { + // Function doesn't have a doc comment or is using the wrong type of comment. + return false; + } + + $comment_start = $tokens[ $comment_end ]['comment_opener']; + foreach ( $tokens[ $comment_start ]['comment_tags'] as $tag ) { + if ( '@deprecated' === $tokens[ $tag ]['content'] ) { + return true; + } + } + + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/EscapingFunctionsTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/EscapingFunctionsTrait.php new file mode 100644 index 00000000..4ebff513 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/EscapingFunctionsTrait.php @@ -0,0 +1,255 @@ + + */ + private $escapingFunctions = array( + 'absint' => true, + 'esc_attr__' => true, + 'esc_attr_e' => true, + 'esc_attr_x' => true, + 'esc_attr' => true, + 'esc_html__' => true, + 'esc_html_e' => true, + 'esc_html_x' => true, + 'esc_html' => true, + 'esc_js' => true, + 'esc_sql' => true, + 'esc_textarea' => true, + 'esc_url_raw' => true, + 'esc_url' => true, + 'esc_xml' => true, + 'filter_input' => true, + 'filter_var' => true, + 'floatval' => true, + 'highlight_string' => true, + 'intval' => true, + 'json_encode' => true, + 'like_escape' => true, + 'number_format' => true, + 'rawurlencode' => true, + 'sanitize_hex_color' => true, + 'sanitize_hex_color_no_hash' => true, + 'sanitize_html_class' => true, + 'sanitize_key' => true, + 'sanitize_user_field' => true, + 'tag_escape' => true, + 'urlencode_deep' => true, + 'urlencode' => true, + 'wp_json_encode' => true, + 'wp_kses_allowed_html' => true, + 'wp_kses_data' => true, + 'wp_kses_one_attr' => true, + 'wp_kses_post' => true, + 'wp_kses' => true, + ); + + /** + * Functions whose output is automatically escaped for display. + * + * @since 0.5.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 - Moved from the Sniff class to this trait. + * - Visibility changed from protected to private. + * + * @var array + */ + private $autoEscapedFunctions = array( + 'allowed_tags' => true, + 'bloginfo' => true, + 'body_class' => true, + 'calendar_week_mod' => true, + 'category_description' => true, + 'checked' => true, + 'comment_class' => true, + 'count' => true, + 'disabled' => true, + 'do_shortcode' => true, + 'do_shortcode_tag' => true, + 'get_archives_link' => true, + 'get_attachment_link' => true, + 'get_avatar' => true, + 'get_bookmark_field' => true, + 'get_calendar' => true, + 'get_comment_author_link' => true, + 'get_current_blog_id' => true, + 'get_delete_post_link' => true, + 'get_search_form' => true, + 'get_search_query' => true, + 'get_the_author_link' => true, + 'get_the_author' => true, + 'get_the_date' => true, + 'get_the_ID' => true, + 'get_the_post_thumbnail' => true, + 'get_the_term_list' => true, + 'post_type_archive_title' => true, + 'readonly' => true, + 'selected' => true, + 'single_cat_title' => true, + 'single_month_title' => true, + 'single_post_title' => true, + 'single_tag_title' => true, + 'single_term_title' => true, + 'tag_description' => true, + 'term_description' => true, + 'the_author' => true, + 'the_date' => true, + 'the_title_attribute' => true, + 'walk_nav_menu_tree' => true, + 'wp_dropdown_categories' => true, + 'wp_dropdown_users' => true, + 'wp_generate_tag_cloud' => true, + 'wp_get_archives' => true, + 'wp_get_attachment_image' => true, + 'wp_get_attachment_link' => true, + 'wp_link_pages' => true, + 'wp_list_authors' => true, + 'wp_list_bookmarks' => true, + 'wp_list_categories' => true, + 'wp_list_comments' => true, + 'wp_login_form' => true, + 'wp_loginout' => true, + 'wp_nav_menu' => true, + 'wp_readonly' => true, + 'wp_register' => true, + 'wp_tag_cloud' => true, + 'wp_timezone_choice' => true, + 'wp_title' => true, + ); + + /** + * Cache of previously added custom functions. + * + * Prevents having to do the same merges over and over again. + * + * @since 0.4.0 + * @since 0.11.0 - Changed from public static to protected non-static. + * - Changed the format from simple bool to array. + * @since 3.0.0 - Moved from the EscapeOutput Sniff class to this trait. + * - Visibility changed from protected to private. + * + * @var array + */ + private $addedCustomEscapingFunctions = array( + 'escape' => array(), + 'autoescape' => array(), + ); + + /** + * Combined list of WP native and custom escaping functions. + * + * @since 3.0.0 + * + * @var array + */ + private $allEscapingFunctions = array(); + + /** + * Combined list of WP native and custom auto-escaping functions. + * + * @since 3.0.0 + * + * @var array + */ + private $allAutoEscapedFunctions = array(); + + /** + * Check if a particular function is regarded as an escaping function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + final public function is_escaping_function( $functionName ) { + if ( array() === $this->allEscapingFunctions + || $this->customEscapingFunctions !== $this->addedCustomEscapingFunctions['escape'] + ) { + $this->allEscapingFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customEscapingFunctions, + $this->escapingFunctions + ); + + $this->addedCustomEscapingFunctions['escape'] = $this->customEscapingFunctions; + } + + return isset( $this->allEscapingFunctions[ strtolower( $functionName ) ] ); + } + + /** + * Check if a particular function is regarded as an auto-escaped function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + final public function is_auto_escaped_function( $functionName ) { + if ( array() === $this->allAutoEscapedFunctions + || $this->customAutoEscapedFunctions !== $this->addedCustomEscapingFunctions['autoescape'] + ) { + $this->allAutoEscapedFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customAutoEscapedFunctions, + $this->autoEscapedFunctions + ); + + $this->addedCustomEscapingFunctions['autoescape'] = $this->customAutoEscapedFunctions; + } + + return isset( $this->allAutoEscapedFunctions[ strtolower( $functionName ) ] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/FormattingFunctionsHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/FormattingFunctionsHelper.php new file mode 100644 index 00000000..e04298ab --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/FormattingFunctionsHelper.php @@ -0,0 +1,60 @@ + + */ + private static $formattingFunctions = array( + 'antispambot' => true, + 'array_fill' => true, + 'ent2ncr' => true, + 'implode' => true, + 'join' => true, + 'nl2br' => true, + 'sprintf' => true, + 'vsprintf' => true, + 'wp_sprintf' => true, + ); + + /** + * Check if a particular function is regarded as a formatting function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + public static function is_formatting_function( $functionName ) { + return isset( self::$formattingFunctions[ strtolower( $functionName ) ] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/IsUnitTestTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/IsUnitTestTrait.php new file mode 100644 index 00000000..a6e3b36d --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/IsUnitTestTrait.php @@ -0,0 +1,237 @@ + + * + * + * + * + * + * + * + * ``` + * + * Note: it is strongly _recommended_ to exclude your test directories for + * select error codes of those particular sniffs instead of relying on this + * property/trait. + * + * @since 0.11.0 + * @since 3.0.0 Moved from the Sniff class to this dedicated Trait. + * Renamed from `$custom_test_class_whitelist` to `$custom_test_classes`. + * + * @var string[] + */ + public $custom_test_classes = array(); + + /** + * List of PHPUnit and WP native classes which test classes can extend. + * + * {internal These are the test cases provided in the `/tests/phpunit/includes/` + * directory of WP Core.} + * + * @since 0.11.0 + * @since 3.0.0 - Moved from the Sniff class to this dedicated Trait. + * - Renamed from `$test_class_whitelist` to `$known_test_classes`. + * - Visibility changed from protected to private. + * + * @var array Key is class name, value irrelevant. + */ + private $known_test_classes = array( + // Base test cases. + 'WP_UnitTestCase' => true, + 'WP_UnitTestCase_Base' => true, + 'PHPUnit_Adapter_TestCase' => true, + + // Domain specific base test cases. + 'WP_Ajax_UnitTestCase' => true, + 'WP_Canonical_UnitTestCase' => true, + 'WP_Test_REST_Controller_Testcase' => true, + 'WP_Test_REST_Post_Type_Controller_Testcase' => true, + 'WP_Test_REST_TestCase' => true, + 'WP_Test_XML_TestCase' => true, + 'WP_XMLRPC_UnitTestCase' => true, + + // PHPUnit native test cases. + 'PHPUnit_Framework_TestCase' => true, + 'PHPUnit\\Framework\\TestCase' => true, + // PHPUnit native TestCase class when imported via use statement. + 'TestCase' => true, + ); + + /** + * Cache of previously added custom test classes. + * + * Prevents having to do the same merges over and over again. + * + * @since 3.0.0 + * + * @var string[] + */ + private $added_custom_test_classes = array(); + + /** + * Combined list of WP/PHPUnit native and custom test classes. + * + * @since 3.0.0 + * + * @var array + */ + private $all_test_classes = array(); + + /** + * Retrieve a list of all registered test classes, both WP/PHPUnit native as well as custom. + * + * @since 3.0.0 + * + * @return array + */ + final protected function get_all_test_classes() { + if ( array() === $this->all_test_classes + || $this->custom_test_classes !== $this->added_custom_test_classes + ) { + /* + * Show some tolerance for user input. + * The custom test class names should be passed as FQN without a prefixing `\`. + */ + $custom_test_classes = array(); + if ( ! empty( $this->custom_test_classes ) ) { + foreach ( $this->custom_test_classes as $v ) { + $custom_test_classes[] = ltrim( $v, '\\' ); + } + } + + /* + * Lowercase all names, both custom as well as "known", as PHP treats namespaced names case-insensitively. + */ + $custom_test_classes = array_map( 'strtolower', $custom_test_classes ); + $known_test_classes = array_change_key_case( $this->known_test_classes, \CASE_LOWER ); + + $this->all_test_classes = RulesetPropertyHelper::merge_custom_array( + $custom_test_classes, + $known_test_classes + ); + + // Store the original value so the comparison can succeed. + $this->added_custom_test_classes = $this->custom_test_classes; + } + + return $this->all_test_classes; + } + + /** + * Check if a class token is part of a unit test suite. + * + * Unit test classes are identified as such: + * - Class which either extends one of the known test cases, such as `WP_UnitTestCase` + * or `PHPUnit_Framework_TestCase` or extends a custom unit test class as listed in the + * `custom_test_classes` property. + * + * @since 0.12.0 Split off from the `is_token_in_test_method()` method. + * @since 1.0.0 Improved recognition of namespaced class names. + * @since 3.0.0 - Moved from the Sniff class to this dedicated Trait. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the token to be examined. + * This should be a class, anonymous class or trait token. + * + * @return bool True if the class is a unit test class, false otherwise. + */ + final protected function is_test_class( File $phpcsFile, $stackPtr ) { + + $tokens = $phpcsFile->getTokens(); + + if ( isset( $tokens[ $stackPtr ], Tokens::$ooScopeTokens[ $tokens[ $stackPtr ]['code'] ] ) === false ) { + return false; + } + + // Add any potentially extra custom test classes to the known test classes list. + $known_test_classes = $this->get_all_test_classes(); + + $namespace = strtolower( Namespaces::determineNamespace( $phpcsFile, $stackPtr ) ); + + // Is the class/trait one of the known test classes ? + $className = ObjectDeclarations::getName( $phpcsFile, $stackPtr ); + if ( empty( $className ) === false ) { + $className = strtolower( $className ); + if ( '' !== $namespace ) { + if ( isset( $known_test_classes[ $namespace . '\\' . $className ] ) ) { + return true; + } + } elseif ( isset( $known_test_classes[ $className ] ) ) { + return true; + } + } + + // Does the class/trait extend one of the known test classes ? + $extendedClassName = ObjectDeclarations::findExtendedClassName( $phpcsFile, $stackPtr ); + if ( false === $extendedClassName ) { + return false; + } + + $extendedClassName = strtolower( $extendedClassName ); + + if ( '\\' === $extendedClassName[0] ) { + if ( isset( $known_test_classes[ substr( $extendedClassName, 1 ) ] ) ) { + return true; + } + } elseif ( '' !== $namespace ) { + if ( isset( $known_test_classes[ $namespace . '\\' . $extendedClassName ] ) ) { + return true; + } + } elseif ( isset( $known_test_classes[ $extendedClassName ] ) ) { + return true; + } + + /* + * Not examining imported classes via `use` statements as with the variety of syntaxes, + * this would get very complicated. + * After all, users can add an `` for a particular sniff to their + * custom ruleset to selectively exclude the test directory. + */ + + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ListHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ListHelper.php new file mode 100644 index 00000000..aa1e2247 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ListHelper.php @@ -0,0 +1,101 @@ +getTokens(); + + // Is this one of the tokens this function handles ? + if ( isset( $tokens[ $stackPtr ], Collections::listOpenTokensBC()[ $tokens[ $stackPtr ]['code'] ] ) === false ) { + return array(); + } + + if ( isset( Collections::shortArrayListOpenTokensBC()[ $tokens[ $stackPtr ]['code'] ] ) + && Lists::isShortList( $phpcsFile, $stackPtr ) === false + ) { + return array(); + } + + try { + $assignments = Lists::getAssignments( $phpcsFile, $stackPtr ); + } catch ( RuntimeException $e ) { + // Parse error/live coding. + return array(); + } + + $var_pointers = array(); + + foreach ( $assignments as $assign ) { + if ( true === $assign['is_empty'] ) { + continue; + } + + if ( true === $assign['is_nested_list'] ) { + /* + * Recurse into the nested list and get the variables. + * No need to `catch` any errors as only lists can be nested in lists. + */ + $var_pointers += self::get_list_variables( $phpcsFile, $assign['assignment_token'] ); + continue; + } + + /* + * Ok, so this must be a "normal" assignment in the list. + * Set the variable pointer both as the key as well as the value, so we can use array join + * for nested lists (above). + */ + $var_pointers[ $assign['assignment_token'] ] = $assign['assignment_token']; + } + + return $var_pointers; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/MinimumWPVersionTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/MinimumWPVersionTrait.php new file mode 100644 index 00000000..92df2a4a --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/MinimumWPVersionTrait.php @@ -0,0 +1,159 @@ + + * + * + * + * + * + * Alternatively, the value can be passed in one go for all sniffs using it via + * the command line or by setting a `` value in a custom phpcs.xml ruleset. + * + * CL: `phpcs --runtime-set minimum_wp_version 5.7` + * Ruleset: `` + * + * @since 0.14.0 Previously the individual sniffs each contained this property. + * @since 3.0.0 - Moved from the Sniff class to this dedicated Trait. + * - The property has been renamed from `$minimum_supported_version` to `$minimum_wp_version`. + * - The CLI option has been renamed from `minimum_supported_wp_version` to `minimum_wp_version`. + * + * @var string WordPress version. + */ + public $minimum_wp_version; + + /** + * Default minimum supported WordPress version. + * + * By default, the minimum_wp_version presumes that a project will support the current + * WP version and up to three releases before. + * + * {@internal This should be a constant, but constants in traits are not supported + * until PHP 8.2.}} + * + * @since 3.0.0 + * + * @var string WordPress version. + */ + private $default_minimum_wp_version = '6.0'; + + /** + * Overrule the minimum supported WordPress version with a command-line/config value. + * + * Handle setting the minimum supported WP version in one go for all sniffs which + * expect it via the command line or via a `` variable in a ruleset. + * The config variable overrules the default `$minimum_wp_version` and/or a + * `$minimum_wp_version` set for individual sniffs through the ruleset. + * + * @since 0.14.0 + * @since 3.0.0 - Moved from the Sniff class to this dedicated Trait. + * - Renamed from `get_wp_version_from_cl()` to `set_minimum_wp_version()`. + * + * @return void + */ + final protected function set_minimum_wp_version() { + $minimum_wp_version = ''; + + // Use a ruleset provided value if available. + if ( ! empty( $this->minimum_wp_version ) ) { + $minimum_wp_version = $this->minimum_wp_version; + } + + // A CLI provided value overrules a ruleset provided value. + $cli_supported_version = Helper::getConfigData( 'minimum_wp_version' ); + if ( ! empty( $cli_supported_version ) ) { + $minimum_wp_version = $cli_supported_version; + } + + // If no valid value was provided, use the default. + if ( filter_var( $minimum_wp_version, \FILTER_VALIDATE_FLOAT ) === false ) { + $minimum_wp_version = $this->default_minimum_wp_version; + } + + $this->minimum_wp_version = $minimum_wp_version; + } + + /** + * Compares two version numbers. + * + * @since 3.0.0 + * + * @param string $version1 First version number. + * @param string $version2 Second version number. + * @param string $operator Comparison operator. + * + * @return bool + */ + final protected function wp_version_compare( $version1, $version2, $operator ) { + $version1 = $this->normalize_version_number( $version1 ); + $version2 = $this->normalize_version_number( $version2 ); + + return version_compare( $version1, $version2, $operator ); + } + + /** + * Normalize a version number. + * + * Ensures that a version number is comparable via the PHP version_compare() function + * by making sure it complies with the minimum "PHP-standardized" version number requirements. + * + * Presumes the input is a numeric version number string. The behaviour with other input is undefined. + * + * @since 3.0.0 + * + * @param string $version Version number. + * + * @return string + */ + private function normalize_version_number( $version ) { + if ( preg_match( '`^\d+\.\d+$`', $version ) ) { + $version .= '.0'; + } + + return $version; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/PrintingFunctionsTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/PrintingFunctionsTrait.php new file mode 100644 index 00000000..edd6dbe2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/PrintingFunctionsTrait.php @@ -0,0 +1,122 @@ + + */ + private $printingFunctions = array( + '_deprecated_argument' => true, + '_deprecated_constructor' => true, + '_deprecated_file' => true, + '_deprecated_function' => true, + '_deprecated_hook' => true, + '_doing_it_wrong' => true, + '_e' => true, + '_ex' => true, + 'printf' => true, + 'trigger_error' => true, + 'user_error' => true, + 'vprintf' => true, + 'wp_die' => true, + 'wp_dropdown_pages' => true, + ); + + /** + * Cache of previously added custom functions. + * + * Prevents having to do the same merges over and over again. + * + * @since 0.4.0 + * @since 0.11.0 - Changed from public static to protected non-static. + * - Changed the format from simple bool to array. + * @since 3.0.0 - Moved from the EscapeOutput Sniff class to this trait. + * - Visibility changed from protected to private. + * + * @var string[] + */ + private $addedCustomPrintingFunctions = array(); + + /** + * Combined list of WP/PHP native and custom printing functions. + * + * @since 3.0.0 + * + * @var array + */ + private $allPrintingFunctions = array(); + + /** + * Retrieve a list of all known printing functions. + * + * @since 3.0.0 + * + * @return array + */ + final public function get_printing_functions() { + if ( array() === $this->allPrintingFunctions + || $this->customPrintingFunctions !== $this->addedCustomPrintingFunctions + ) { + $this->allPrintingFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customPrintingFunctions, + $this->printingFunctions + ); + + $this->addedCustomPrintingFunctions = $this->customPrintingFunctions; + } + + return $this->allPrintingFunctions; + } + + /** + * Check if a particular function is regarded as a printing function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + final public function is_printing_function( $functionName ) { + return isset( $this->get_printing_functions()[ strtolower( $functionName ) ] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/RulesetPropertyHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/RulesetPropertyHelper.php new file mode 100644 index 00000000..b20c6ed3 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/RulesetPropertyHelper.php @@ -0,0 +1,73 @@ + true` format. + * * Any custom items will be given the value `false` to be able to + * distinguish them from pre-set (base array) values. + * * Will filter previously added custom items out from the base array + * before merging/returning to allow for resetting to the base array. + * + * {@internal Function is static as it doesn't use any of the properties or others + * methods anyway.} + * + * @since 0.11.0 + * @since 2.0.0 No longer supports custom array properties which were incorrectly + * passed as a string. + * @since 3.0.0 Moved from the Sniff class to this class. + * + * @param array $custom Custom list as provided via a ruleset. + * @param array $base Optional. Base list. Defaults to an empty array. + * Expects `value => true` format when `$flip` is true. + * @param bool $flip Optional. Whether or not to flip the custom list. + * Defaults to true. + * @return array + */ + public static function merge_custom_array( $custom, array $base = array(), $flip = true ) { + if ( true === $flip ) { + $base = array_filter( $base ); + } + + if ( empty( $custom ) || ! \is_array( $custom ) ) { + return $base; + } + + if ( true === $flip ) { + $custom = array_fill_keys( $custom, false ); + } + + if ( empty( $base ) ) { + return $custom; + } + + return array_merge( $base, $custom ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SanitizationHelperTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SanitizationHelperTrait.php new file mode 100644 index 00000000..c041dfe5 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SanitizationHelperTrait.php @@ -0,0 +1,417 @@ + + */ + private $sanitizingFunctions = array( + '_wp_handle_upload' => true, + 'esc_url_raw' => true, + 'filter_input' => true, + 'filter_var' => true, + 'hash_equals' => true, + 'is_email' => true, + 'number_format' => true, + 'sanitize_bookmark_field' => true, + 'sanitize_bookmark' => true, + 'sanitize_email' => true, + 'sanitize_file_name' => true, + 'sanitize_hex_color_no_hash' => true, + 'sanitize_hex_color' => true, + 'sanitize_html_class' => true, + 'sanitize_meta' => true, + 'sanitize_mime_type' => true, + 'sanitize_option' => true, + 'sanitize_sql_orderby' => true, + 'sanitize_term_field' => true, + 'sanitize_term' => true, + 'sanitize_text_field' => true, + 'sanitize_textarea_field' => true, + 'sanitize_title_for_query' => true, + 'sanitize_title_with_dashes' => true, + 'sanitize_title' => true, + 'sanitize_url' => true, + 'sanitize_user_field' => true, + 'sanitize_user' => true, + 'validate_file' => true, + 'wp_handle_sideload' => true, + 'wp_handle_upload' => true, + 'wp_kses_allowed_html' => true, + 'wp_kses_data' => true, + 'wp_kses_one_attr' => true, + 'wp_kses_post' => true, + 'wp_kses' => true, + 'wp_parse_id_list' => true, + 'wp_redirect' => true, + 'wp_safe_redirect' => true, + 'wp_sanitize_redirect' => true, + 'wp_strip_all_tags' => true, + ); + + /** + * Sanitizing functions that implicitly unslash the data passed to them. + * + * This list is complementary to the `$sanitizingFunctions` list. + * Sanitizing functions should be added to this list if they also + * implicitely unslash data and to the `$sanitizingFunctions` list + * if they don't. + * + * @since 0.5.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 - Moved from the Sniff class to this trait. + * - Visibility changed from protected to private. + * + * @var array + */ + private $unslashingSanitizingFunctions = array( + 'absint' => true, + 'boolval' => true, + 'count' => true, + 'doubleval' => true, + 'floatval' => true, + 'intval' => true, + 'sanitize_key' => true, + 'sizeof' => true, + ); + + /** + * Cache of previously added custom functions. + * + * Prevents having to do the same merges over and over again. + * + * @since 0.4.0 + * @since 0.11.0 - Changed from public static to protected non-static. + * - Changed the format from simple bool to array. + * @since 3.0.0 - Moved from the NonceVerification and the ValidatedSanitizedInput sniff classes to this class. + * - Visibility changed from protected to private. + * + * @var array + */ + private $addedCustomSanitizingFunctions = array( + 'sanitize' => array(), + 'unslashsanitize' => array(), + ); + + /** + * Combined list of WP/PHP native and custom sanitizing functions. + * + * @since 3.0.0 + * + * @var array + */ + private $allSanitizingFunctions = array(); + + /** + * Combined list of WP/PHP native and custom sanitizing and unslashing functions. + * + * @since 3.0.0 + * + * @var array + */ + private $allUnslashingSanitizingFunctions = array(); + + /** + * Retrieve a list of all known sanitizing functions. + * + * @since 3.0.0 + * + * @return array + */ + final public function get_sanitizing_functions() { + if ( array() === $this->allSanitizingFunctions + || $this->customSanitizingFunctions !== $this->addedCustomSanitizingFunctions['sanitize'] + ) { + $this->allSanitizingFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customSanitizingFunctions, + $this->sanitizingFunctions + ); + + $this->addedCustomSanitizingFunctions['sanitize'] = $this->customSanitizingFunctions; + } + + return $this->allSanitizingFunctions; + } + + /** + * Retrieve a list of all known sanitizing and unslashing functions. + * + * @since 3.0.0 + * + * @return array + */ + final public function get_sanitizing_and_unslashing_functions() { + if ( array() === $this->allUnslashingSanitizingFunctions + || $this->customUnslashingSanitizingFunctions !== $this->addedCustomSanitizingFunctions['unslashsanitize'] + ) { + $this->allUnslashingSanitizingFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customUnslashingSanitizingFunctions, + $this->unslashingSanitizingFunctions + ); + + $this->addedCustomSanitizingFunctions['unslashsanitize'] = $this->customUnslashingSanitizingFunctions; + } + + return $this->allUnslashingSanitizingFunctions; + } + + /** + * Check if a particular function is regarded as a sanitizing function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + final public function is_sanitizing_function( $functionName ) { + return isset( $this->get_sanitizing_functions()[ strtolower( $functionName ) ] ); + } + + /** + * Check if a particular function is regarded as a sanitizing and unslashing function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + final public function is_sanitizing_and_unslashing_function( $functionName ) { + return isset( $this->get_sanitizing_and_unslashing_functions()[ strtolower( $functionName ) ] ); + } + + /** + * Check if something is only being sanitized. + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool Whether the token is only within a sanitization. + */ + final public function is_only_sanitized( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // If it isn't being sanitized at all. + if ( ! $this->is_sanitized( $phpcsFile, $stackPtr ) ) { + return false; + } + + // If the token isn't in parentheses, we know the value must have only been casted, because + // is_sanitized() would have returned `false` otherwise. + if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { + return true; + } + + // At this point we're expecting the value to have not been casted. If it + // was, it wasn't *only* casted, because it's also in a function. + if ( ContextHelper::is_safe_casted( $phpcsFile, $stackPtr ) ) { + return false; + } + + // The only parentheses should belong to the sanitizing function. If there's + // more than one set, this isn't *only* sanitization. + return ( \count( $tokens[ $stackPtr ]['nested_parenthesis'] ) === 1 ); + } + + /** + * Check if something is being sanitized. + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public`. + * - The `$phpcsFile` parameter was added. + * - The $require_unslash parameter has been changed from + * a boolean toggle to a ?callable $unslash_callback parameter to + * allow a sniff calling this method to handle their "unslashing" + * related messaging itself. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * @param callable|null $unslash_callback Optional. When passed, this method will check if + * an unslashing function is used on the variable before + * sanitization and if not, the callback will be called + * to handle the missing unslashing. + * The callback will receive the $phpcsFile object and + * the $stackPtr. + * When not passed or `null`, this method will **not** + * check for unslashing issues. + * Defaults to `null` (skip unslashing checks). + * + * @return bool Whether the token is being sanitized. + */ + final public function is_sanitized( File $phpcsFile, $stackPtr, $unslash_callback = null ) { + $tokens = $phpcsFile->getTokens(); + $require_unslash = is_callable( $unslash_callback ); + + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + // If the variable is just being unset, the value isn't used at all, so it's safe. + if ( Context::inUnset( $phpcsFile, $stackPtr ) ) { + return true; + } + + // First we check if it is being casted to a safe value. + if ( ContextHelper::is_safe_casted( $phpcsFile, $stackPtr ) ) { + return true; + } + + // If this isn't within a function call, we know already that it's not safe. + if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { + if ( $require_unslash ) { + call_user_func( $unslash_callback, $phpcsFile, $stackPtr ); + } + + return false; + } + + $sanitizing_functions = $this->get_sanitizing_functions(); + $sanitizing_functions += $this->get_sanitizing_and_unslashing_functions(); + $sanitizing_functions += ArrayWalkingFunctionsHelper::get_functions(); + $valid_functions = $sanitizing_functions + UnslashingFunctionsHelper::get_functions(); + + // Get the function that it's in. + $functionPtr = ContextHelper::is_in_function_call( $phpcsFile, $stackPtr, $valid_functions ); + + // If this isn't a call to one of the valid functions, it sure isn't a sanitizing function. + if ( false === $functionPtr ) { + if ( true === $require_unslash ) { + call_user_func( $unslash_callback, $phpcsFile, $stackPtr ); + } + + return false; + } + + $functionName = $tokens[ $functionPtr ]['content']; + + // Check if an unslashing function is being used. + $is_unslashed = false; + if ( UnslashingFunctionsHelper::is_unslashing_function( $functionName ) ) { + $is_unslashed = true; + + // Check whether this function call is wrapped within a sanitizing function. + $higherFunctionPtr = ContextHelper::is_in_function_call( $phpcsFile, $functionPtr, $sanitizing_functions ); + + // If there is no other valid function being used, this value is unsanitized. + if ( false === $higherFunctionPtr ) { + return false; + } + + $functionPtr = $higherFunctionPtr; + $functionName = $tokens[ $functionPtr ]['content']; + } + + // Arrays might be sanitized via an array walking function using a callback. + if ( ArrayWalkingFunctionsHelper::is_array_walking_function( $functionName ) ) { + // Get the callback parameter. + $callback = ArrayWalkingFunctionsHelper::get_callback_parameter( $phpcsFile, $functionPtr ); + + if ( ! empty( $callback ) ) { + /* + * If this is a function callback (not a method callback array) and we're able + * to resolve the function name, do so. + */ + $first_non_empty = $phpcsFile->findNext( + Tokens::$emptyTokens, + $callback['start'], + ( $callback['end'] + 1 ), + true + ); + + if ( false !== $first_non_empty && \T_CONSTANT_ENCAPSED_STRING === $tokens[ $first_non_empty ]['code'] ) { + $functionName = TextStrings::stripQuotes( $tokens[ $first_non_empty ]['content'] ); + } + } + } + + // If slashing is required, give an error. + if ( false === $is_unslashed + && true === $require_unslash + && ! $this->is_sanitizing_and_unslashing_function( $functionName ) + ) { + call_user_func( $unslash_callback, $phpcsFile, $stackPtr ); + } + + // Check if this is a sanitizing function. + return ( $this->is_sanitizing_function( $functionName ) || $this->is_sanitizing_and_unslashing_function( $functionName ) ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SnakeCaseHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SnakeCaseHelper.php new file mode 100644 index 00000000..9937e5e2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SnakeCaseHelper.php @@ -0,0 +1,60 @@ + + */ + private static $unslashingFunctions = array( + 'stripslashes_deep' => true, + 'stripslashes_from_strings_only' => true, + 'wp_unslash' => true, + ); + + /** + * Retrieve a list of the unslashing functions. + * + * @since 3.0.0 + * + * @return array + */ + public static function get_functions() { + return self::$unslashingFunctions; + } + + /** + * Check if a particular function is regarded as a unslashing function. + * + * @since 3.0.0 + * + * @param string $functionName The name of the function to check. + * + * @return bool + */ + public static function is_unslashing_function( $functionName ) { + return isset( self::$unslashingFunctions[ strtolower( $functionName ) ] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ValidationHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ValidationHelper.php new file mode 100644 index 00000000..d5a22d7d --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/ValidationHelper.php @@ -0,0 +1,349 @@ + + */ + private static $targets = array( + \T_ISSET => 'construct', + \T_EMPTY => 'construct', + \T_STRING => 'function_call', + \T_COALESCE => 'coalesce', + \T_COALESCE_EQUAL => 'coalesce', + ); + + /** + * List of PHP native functions to check if an array index exists. + * + * @since 3.0.0 + * + * @var array + */ + private static $key_exists_functions = array( + 'array_key_exists' => true, + 'key_exists' => true, // Alias. + ); + + /** + * Check if the existence of a variable is validated with isset(), empty(), array_key_exists() + * or key_exists(). + * + * When $in_condition_only is `false`, (which is the default), this is considered + * valid: + * + * ```php + * if ( isset( $var ) ) { + * // Do stuff, like maybe return or exit (but could be anything) + * } + * + * foo( $var ); + * ``` + * + * When it is `true`, that would be invalid; the use of the variable must be within + * the scope of the validating condition, like this: + * + * ```php + * if ( isset( $var ) ) { + * foo( $var ); + * } + * ``` + * + * @since 0.5.0 + * @since 2.1.0 Now recognizes array_key_exists() and key_exists() as validation functions. + * @since 2.1.0 Stricter check on whether the correct variable and the correct + * array keys are being validated. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The method visibility was changed from `protected` to `public static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of this token in the stack. + * @param array|string $array_keys An array key to check for ("bar" in $foo['bar']) + * or an array of keys for multi-level array access. + * @param bool $in_condition_only Whether to require that this use of the + * variable occurs within the scope of the + * validating condition, or just in the same + * scope (default). + * + * @return bool Whether the var is validated. + */ + public static function is_validated( File $phpcsFile, $stackPtr, $array_keys = array(), $in_condition_only = false ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + if ( $in_condition_only ) { + /* + * This is a stricter check, requiring the variable to be used only + * within the validation condition. + */ + $conditionPtr = Conditions::getLastCondition( $phpcsFile, $stackPtr ); + if ( false === $conditionPtr ) { + // If there are no conditions, there's no validation. + return false; + } + + $condition = $tokens[ $conditionPtr ]; + if ( ! isset( $condition['parenthesis_opener'] ) ) { + // Live coding or parse error. + return false; + } + + $scope_start = $condition['parenthesis_opener']; + $scope_end = $condition['parenthesis_closer']; + + } else { + /* + * We are more loose, requiring only that the variable be validated + * in the same function/file scope as it is used. + */ + $scope_start = 0; + + /* + * Check if we are in a function. + * + * Note: PHP 7.4+ arrow functions are not taken into account as those are not + * included in the "conditions" array. Additionally, arrow functions have + * access to variables outside their direct scope. + */ + $function = Conditions::getLastCondition( $phpcsFile, $stackPtr, array( \T_FUNCTION, \T_CLOSURE ) ); + + // If so, we check only within the function, otherwise the whole file. + if ( false !== $function ) { + $scope_start = $tokens[ $function ]['scope_opener']; + } + + $scope_end = $stackPtr; + } + + if ( ! empty( $array_keys ) && ! is_array( $array_keys ) ) { + $array_keys = (array) $array_keys; + } + + $bare_array_keys = self::strip_quotes_from_array_values( $array_keys ); + + // phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer.Found -- On purpose, see below. + for ( $i = ( $scope_start + 1 ); $i < $scope_end; $i++ ) { + + if ( isset( Collections::closedScopes()[ $tokens[ $i ]['code'] ] ) + && isset( $tokens[ $i ]['scope_closer'] ) + ) { + // Jump over nested closed scopes as validation done within those does not apply. + $i = $tokens[ $i ]['scope_closer']; + continue; + } + + if ( \T_FN === $tokens[ $i ]['code'] + && isset( $tokens[ $i ]['scope_closer'] ) + && $tokens[ $i ]['scope_closer'] < $scope_end + ) { + // Jump over nested arrow functions as long as the current variable isn't *in* the arrow function. + $i = $tokens[ $i ]['scope_closer']; + continue; + } + + if ( isset( self::$targets[ $tokens[ $i ]['code'] ] ) === false ) { + continue; + } + + switch ( self::$targets[ $tokens[ $i ]['code'] ] ) { + case 'construct': + $issetOpener = $phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( false === $issetOpener + || \T_OPEN_PARENTHESIS !== $tokens[ $issetOpener ]['code'] + || isset( $tokens[ $issetOpener ]['parenthesis_closer'] ) === false + ) { + // Parse error or live coding. + continue 2; + } + + $issetCloser = $tokens[ $issetOpener ]['parenthesis_closer']; + + // Look for this variable. We purposely stomp $i from the parent loop. + for ( $i = ( $issetOpener + 1 ); $i < $issetCloser; $i++ ) { + + if ( \T_VARIABLE !== $tokens[ $i ]['code'] ) { + continue; + } + + if ( $tokens[ $stackPtr ]['content'] !== $tokens[ $i ]['content'] ) { + continue; + } + + // If we're checking for specific array keys (ex: 'hello' in + // $_POST['hello']), that must match too. Quote-style, however, doesn't matter. + if ( ! empty( $bare_array_keys ) ) { + $found_keys = VariableHelper::get_array_access_keys( $phpcsFile, $i ); + $found_keys = self::strip_quotes_from_array_values( $found_keys ); + $diff = array_diff_assoc( $bare_array_keys, $found_keys ); + if ( ! empty( $diff ) ) { + continue; + } + } + + return true; + } + + break; + + case 'function_call': + // Only check calls to array_key_exists() and key_exists(). + if ( isset( self::$key_exists_functions[ strtolower( $tokens[ $i ]['content'] ) ] ) === false ) { + continue 2; + } + + $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( false === $next_non_empty || \T_OPEN_PARENTHESIS !== $tokens[ $next_non_empty ]['code'] ) { + // Not a function call. + continue 2; + } + + if ( Context::inAttribute( $phpcsFile, $i ) === true ) { + // Definitely not the function call as those are not allowed in attributes. + continue 2; + } + + if ( ContextHelper::has_object_operator_before( $phpcsFile, $i ) === true ) { + // Method call. + continue 2; + } + + if ( ContextHelper::is_token_namespaced( $phpcsFile, $i ) === true ) { + // Namespaced function call. + continue 2; + } + + $params = PassedParameters::getParameters( $phpcsFile, $i ); + + // As `key_exists()` is an alias of `array_key_exists()`, the param positions and names are the same. + $array_param = PassedParameters::getParameterFromStack( $params, 2, 'array' ); + if ( false === $array_param ) { + continue 2; + } + + $array_param_first_token = $phpcsFile->findNext( Tokens::$emptyTokens, $array_param['start'], ( $array_param['end'] + 1 ), true ); + if ( false === $array_param_first_token + || \T_VARIABLE !== $tokens[ $array_param_first_token ]['code'] + || $tokens[ $array_param_first_token ]['content'] !== $tokens[ $stackPtr ]['content'] + ) { + continue 2; + } + + if ( ! empty( $bare_array_keys ) ) { + // Prevent the original array from being altered. + $bare_keys = $bare_array_keys; + $last_key = array_pop( $bare_keys ); + + /* + * For multi-level array access, the complete set of keys could be split between + * the $key and the $array parameter, but could also be completely in the $array + * parameter, so we need to check both options. + */ + $found_keys = VariableHelper::get_array_access_keys( $phpcsFile, $array_param_first_token ); + $found_keys = self::strip_quotes_from_array_values( $found_keys ); + + // First try matching the complete set against the array parameter. + $diff = array_diff_assoc( $bare_array_keys, $found_keys ); + if ( empty( $diff ) ) { + return true; + } + + // If that failed, try getting an exact match for the subset against the + // $array parameter and the last key against the first. + $key_param = PassedParameters::getParameterFromStack( $params, 1, 'key' ); + if ( false !== $key_param + && $bare_keys === $found_keys + && TextStrings::stripQuotes( $key_param['raw'] ) === $last_key + ) { + return true; + } + + // Didn't find the correct array keys. + continue 2; + } + + return true; + + case 'coalesce': + $prev = $i; + do { + $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true ); + // Skip over array keys, like `$_GET['key']['subkey']`. + if ( \T_CLOSE_SQUARE_BRACKET === $tokens[ $prev ]['code'] ) { + $prev = $tokens[ $prev ]['bracket_opener']; + continue; + } + + break; + } while ( $prev >= ( $scope_start + 1 ) ); + + // We should now have reached the variable. + if ( \T_VARIABLE !== $tokens[ $prev ]['code'] ) { + continue 2; + } + + if ( $tokens[ $prev ]['content'] !== $tokens[ $stackPtr ]['content'] ) { + continue 2; + } + + if ( ! empty( $bare_array_keys ) ) { + $found_keys = VariableHelper::get_array_access_keys( $phpcsFile, $prev ); + $found_keys = self::strip_quotes_from_array_values( $found_keys ); + $diff = array_diff_assoc( $bare_array_keys, $found_keys ); + if ( ! empty( $diff ) ) { + continue 2; + } + } + + // Right variable, correct key. + return true; + } + } + + return false; + } + + /** + * Strip quotes of all the values in an array containing only text strings. + * + * @since 3.0.0 + * + * @param string[] $text_strings The input array. + * + * @return string[] + */ + private static function strip_quotes_from_array_values( array $text_strings ) { + return array_map( array( 'PHPCSUtils\Utils\TextStrings', 'stripQuotes' ), $text_strings ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/VariableHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/VariableHelper.php new file mode 100644 index 00000000..e1076ee4 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/VariableHelper.php @@ -0,0 +1,262 @@ +getTokens(); + $keys = array(); + + if ( isset( $tokens[ $stackPtr ] ) === false + || \T_VARIABLE !== $tokens[ $stackPtr ]['code'] + ) { + return $keys; + } + + $current = $stackPtr; + + do { + // Find the next non-empty token. + $open_bracket = $phpcsFile->findNext( + Tokens::$emptyTokens, + ( $current + 1 ), + null, + true + ); + + // If it isn't a bracket, this isn't an array-access. + if ( false === $open_bracket + || \T_OPEN_SQUARE_BRACKET !== $tokens[ $open_bracket ]['code'] + || ! isset( $tokens[ $open_bracket ]['bracket_closer'] ) + ) { + break; + } + + $key = GetTokensAsString::compact( + $phpcsFile, + ( $open_bracket + 1 ), + ( $tokens[ $open_bracket ]['bracket_closer'] - 1 ), + true + ); + + $keys[] = trim( $key ); + $current = $tokens[ $open_bracket ]['bracket_closer']; + } while ( isset( $tokens[ $current ] ) && true === $all ); + + return $keys; + } + + /** + * Get the index key of an array variable. + * + * E.g., "bar" in $foo['bar']. + * + * @since 0.5.0 + * @since 2.1.0 Now uses get_array_access_keys() under the hood. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - Visibility is now `public` (was `protected`) and the method `static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the variable token in the stack. + * + * @return string|false The array index key whose value is being accessed. + */ + public static function get_array_access_key( File $phpcsFile, $stackPtr ) { + $keys = self::get_array_access_keys( $phpcsFile, $stackPtr, false ); + if ( isset( $keys[0] ) ) { + return $keys[0]; + } + + return false; + } + + /** + * Check whether a variable is being compared to another value. + * + * E.g., $var === 'foo', 1 <= $var, etc. + * + * Also recognizes `switch ( $var )` and `match ( $var )`. + * + * @since 0.5.0 + * @since 2.1.0 Added the $include_coalesce parameter. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - Visibility is now `public` (was `protected`) and the method `static`. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of this token in the stack. + * @param bool $include_coalesce Optional. Whether or not to regard the null + * coalesce operator - ?? - as a comparison operator. + * Defaults to true. + * Null coalesce is a special comparison operator in this + * sense as it doesn't compare a variable to whatever is + * on the other side of the comparison operator. + * + * @return bool Whether this is a comparison. + */ + public static function is_comparison( File $phpcsFile, $stackPtr, $include_coalesce = true ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + $comparisonTokens = Tokens::$comparisonTokens; + if ( false === $include_coalesce ) { + unset( $comparisonTokens[ \T_COALESCE ] ); + } + + // We first check if this is a switch or match statement (switch ( $var )). + if ( Parentheses::lastOwnerIn( $phpcsFile, $stackPtr, array( \T_SWITCH, \T_MATCH ) ) !== false ) { + return true; + } + + // Find the previous non-empty token. We check before the var first because + // yoda conditions are usually expected. + $previous_token = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + + if ( isset( $comparisonTokens[ $tokens[ $previous_token ]['code'] ] ) ) { + return true; + } + + // Maybe the comparison operator is after this. + $next_token = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + + // This might be an opening square bracket in the case of arrays ($var['a']). + while ( false !== $next_token + && \T_OPEN_SQUARE_BRACKET === $tokens[ $next_token ]['code'] + && isset( $tokens[ $next_token ]['bracket_closer'] ) + ) { + $next_token = $phpcsFile->findNext( + Tokens::$emptyTokens, + ( $tokens[ $next_token ]['bracket_closer'] + 1 ), + null, + true + ); + } + + if ( false !== $next_token && isset( $comparisonTokens[ $tokens[ $next_token ]['code'] ] ) ) { + return true; + } + + return false; + } + + /** + * Check if this variable is being assigned a value. + * + * E.g., $var = 'foo'; + * + * Also handles array assignments to arbitrary depth: + * + * $array['key'][ $foo ][ something() ] = $bar; + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the Sniff class to this class. + * - Visibility is now `public` (was `protected`) and the method `static`. + * - The `$phpcsFile` parameter was added. + * - The `$include_coalesce` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * This must point to either a T_VARIABLE or + * T_CLOSE_SQUARE_BRACKET token. + * @param bool $include_coalesce Optional. Whether or not to regard the null + * coalesce operator - ?? - as a comparison operator. + * Defaults to true. + * Null coalesce is a special comparison operator in this + * sense as it doesn't compare a variable to whatever is + * on the other side of the comparison operator. + * + * @return bool Whether the token is a variable being assigned a value. + */ + public static function is_assignment( File $phpcsFile, $stackPtr, $include_coalesce = true ) { + $tokens = $phpcsFile->getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + static $valid = array( + \T_VARIABLE => true, + \T_CLOSE_SQUARE_BRACKET => true, + ); + + // Must be a variable or closing square bracket (see below). + if ( ! isset( $valid[ $tokens[ $stackPtr ]['code'] ] ) ) { + return false; + } + + $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + + // No token found. + if ( false === $next_non_empty ) { + return false; + } + + $assignmentTokens = Tokens::$assignmentTokens; + if ( false === $include_coalesce ) { + unset( $assignmentTokens[ \T_COALESCE_EQUAL ] ); + } + + // If the next token is an assignment, that's all we need to know. + if ( isset( $assignmentTokens[ $tokens[ $next_non_empty ]['code'] ] ) ) { + return true; + } + + // Check if this is an array assignment, e.g., `$var['key'] = 'val';` . + if ( \T_OPEN_SQUARE_BRACKET === $tokens[ $next_non_empty ]['code'] + && isset( $tokens[ $next_non_empty ]['bracket_closer'] ) + ) { + return self::is_assignment( $phpcsFile, $tokens[ $next_non_empty ]['bracket_closer'], $include_coalesce ); + } + + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPDBTrait.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPDBTrait.php new file mode 100644 index 00000000..1f325eff --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPDBTrait.php @@ -0,0 +1,115 @@ +getTokens(); + if ( isset( $tokens[ $stackPtr ] ) === false ) { + return false; + } + + // Check for wpdb. + if ( ( \T_VARIABLE === $tokens[ $stackPtr ]['code'] && '$wpdb' !== $tokens[ $stackPtr ]['content'] ) + || ( \T_STRING === $tokens[ $stackPtr ]['code'] && 'wpdb' !== strtolower( $tokens[ $stackPtr ]['content'] ) ) + ) { + return false; + } + + // Check that this is a method call. + $is_object_call = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false === $is_object_call + || isset( Collections::objectOperators()[ $tokens[ $is_object_call ]['code'] ] ) === false + ) { + return false; + } + + $methodPtr = $phpcsFile->findNext( Tokens::$emptyTokens, ( $is_object_call + 1 ), null, true, null, true ); + if ( false === $methodPtr ) { + return false; + } + + if ( \T_STRING === $tokens[ $methodPtr ]['code'] && property_exists( $this, 'methodPtr' ) ) { + $this->methodPtr = $methodPtr; + } + + // Find the opening parenthesis. + $opening_paren = $phpcsFile->findNext( Tokens::$emptyTokens, ( $methodPtr + 1 ), null, true, null, true ); + + if ( false === $opening_paren ) { + return false; + } + + if ( property_exists( $this, 'i' ) ) { + $this->i = $opening_paren; + } + + if ( \T_OPEN_PARENTHESIS !== $tokens[ $opening_paren ]['code'] + || ! isset( $tokens[ $opening_paren ]['parenthesis_closer'] ) + ) { + return false; + } + + // Check that this is one of the methods that we are interested in. + if ( ! isset( $target_methods[ strtolower( $tokens[ $methodPtr ]['content'] ) ] ) ) { + return false; + } + + // Find the end of the first parameter. + $end = BCFile::findEndOfStatement( $phpcsFile, $opening_paren + 1 ); + + if ( \T_COMMA !== $tokens[ $end ]['code'] ) { + ++$end; + } + + if ( property_exists( $this, 'end' ) ) { + $this->end = $end; + } + + return true; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPGlobalVariablesHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPGlobalVariablesHelper.php new file mode 100644 index 00000000..b5097931 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPGlobalVariablesHelper.php @@ -0,0 +1,312 @@ + The key is the name of a WP global variable, the value is irrelevant. + */ + private static $wp_globals = array( + '_links_add_base' => true, + '_links_add_target' => true, + '_menu_item_sort_prop' => true, + '_nav_menu_placeholder' => true, + '_new_bundled_files' => true, + '_old_files' => true, + '_parent_pages' => true, + '_registered_pages' => true, + '_updated_user_settings' => true, + '_wp_additional_image_sizes' => true, + '_wp_admin_css_colors' => true, + '_wp_default_headers' => true, + '_wp_deprecated_widgets_callbacks' => true, + '_wp_last_object_menu' => true, + '_wp_last_utility_menu' => true, + '_wp_menu_nopriv' => true, + '_wp_nav_menu_max_depth' => true, + '_wp_post_type_features' => true, + '_wp_real_parent_file' => true, + '_wp_registered_nav_menus' => true, + '_wp_sidebars_widgets' => true, + '_wp_submenu_nopriv' => true, + '_wp_suspend_cache_invalidation' => true, + '_wp_theme_features' => true, + '_wp_using_ext_object_cache' => true, + 'action' => true, + 'active_signup' => true, + 'admin_body_class' => true, + 'admin_page_hooks' => true, + 'all_links' => true, + 'allowedentitynames' => true, + 'allowedposttags' => true, + 'allowedtags' => true, + 'auth_secure_cookie' => true, + 'authordata' => true, + 'avail_post_mime_types' => true, + 'avail_post_stati' => true, + 'blog_id' => true, + 'blog_title' => true, + 'blogname' => true, + 'cat' => true, + 'cat_id' => true, + 'charset_collate' => true, + 'comment' => true, + 'comment_alt' => true, + 'comment_depth' => true, + 'comment_status' => true, + 'comment_thread_alt' => true, + 'comment_type' => true, + 'comments' => true, + 'compress_css' => true, + 'compress_scripts' => true, + 'concatenate_scripts' => true, + 'content_width' => true, + 'current_blog' => true, + 'current_screen' => true, + 'current_site' => true, + 'current_user' => true, + 'currentcat' => true, + 'currentday' => true, + 'currentmonth' => true, + 'custom_background' => true, + 'custom_image_header' => true, + 'default_menu_order' => true, + 'descriptions' => true, + 'domain' => true, + 'editor_styles' => true, + 'error' => true, + 'errors' => true, + 'EZSQL_ERROR' => true, + 'feeds' => true, + 'GETID3_ERRORARRAY' => true, + 'hook_suffix' => true, + 'HTTP_RAW_POST_DATA' => true, + 'id' => true, + 'in_comment_loop' => true, + 'interim_login' => true, + 'is_apache' => true, + 'is_chrome' => true, + 'is_gecko' => true, + 'is_IE' => true, + 'is_IIS' => true, + 'is_iis7' => true, + 'is_macIE' => true, + 'is_NS4' => true, + 'is_opera' => true, + 'is_safari' => true, + 'is_winIE' => true, + 'l10n' => true, + 'link' => true, + 'link_id' => true, + 'locale' => true, + 'locked_post_status' => true, + 'lost' => true, + 'm' => true, + 'map' => true, + 'menu' => true, + 'menu_order' => true, + 'merged_filters' => true, + 'mode' => true, + 'monthnum' => true, + 'more' => true, + 'mu_plugin' => true, + 'multipage' => true, + 'names' => true, + 'nav_menu_selected_id' => true, + 'network_plugin' => true, + 'new_whitelist_options' => true, + 'numpages' => true, + 'one_theme_location_no_menus' => true, + 'opml' => true, + 'order' => true, + 'orderby' => true, + 'overridden_cpage' => true, + 'page' => true, + 'paged' => true, + 'pagenow' => true, + 'pages' => true, + 'parent_file' => true, + 'pass_allowed_html' => true, + 'pass_allowed_protocols' => true, + 'path' => true, + 'per_page' => true, + 'PHP_SELF' => true, + 'phpmailer' => true, + 'plugin_page' => true, + 'plugin' => true, + 'plugins' => true, + 'post' => true, + 'post_default_category' => true, + 'post_default_title' => true, + 'post_ID' => true, + 'post_id' => true, + 'post_mime_types' => true, + 'post_type' => true, + 'post_type_object' => true, + 'posts' => true, + 'preview' => true, + 'previouscat' => true, + 'previousday' => true, + 'previousweekday' => true, + 'redir_tab' => true, + 'required_mysql_version' => true, + 'required_php_version' => true, + 'rnd_value' => true, + 'role' => true, + 's' => true, + 'search' => true, + 'self' => true, + 'shortcode_tags' => true, + 'show_admin_bar' => true, + 'sidebars_widgets' => true, + 'status' => true, + 'submenu' => true, + 'submenu_file' => true, + 'super_admins' => true, + 'tab' => true, + 'table_prefix' => true, + 'tabs' => true, + 'tag' => true, + 'tag_ID' => true, + 'targets' => true, + 'tax' => true, + 'taxnow' => true, + 'taxonomy' => true, + 'term' => true, + 'text_direction' => true, + 'theme_field_defaults' => true, + 'themes_allowedtags' => true, + 'timeend' => true, + 'timestart' => true, + 'tinymce_version' => true, + 'title' => true, + 'totals' => true, + 'type' => true, + 'typenow' => true, + 'updated_timestamp' => true, + 'upgrading' => true, + 'urls' => true, + 'user_email' => true, + 'user_ID' => true, + 'user_identity' => true, + 'user_level' => true, + 'user_login' => true, + 'user_url' => true, + 'userdata' => true, + 'usersearch' => true, + 'whitelist_options' => true, + 'withcomments' => true, + 'wp' => true, + 'wp_actions' => true, + 'wp_admin_bar' => true, + 'wp_cockneyreplace' => true, + 'wp_current_db_version' => true, + 'wp_current_filter' => true, + 'wp_customize' => true, + 'wp_dashboard_control_callbacks' => true, + 'wp_db_version' => true, + 'wp_did_header' => true, + 'wp_embed' => true, + 'wp_file_descriptions' => true, + 'wp_filesystem' => true, + 'wp_filter' => true, + 'wp_hasher' => true, + 'wp_header_to_desc' => true, + 'wp_importers' => true, + 'wp_json' => true, + 'wp_list_table' => true, + 'wp_local_package' => true, + 'wp_locale' => true, + 'wp_meta_boxes' => true, + 'wp_object_cache' => true, + 'wp_plugin_paths' => true, + 'wp_post_statuses' => true, + 'wp_post_types' => true, + 'wp_queries' => true, + 'wp_query' => true, + 'wp_registered_sidebars' => true, + 'wp_registered_widget_controls' => true, + 'wp_registered_widget_updates' => true, + 'wp_registered_widgets' => true, + 'wp_rewrite' => true, + 'wp_rich_edit' => true, + 'wp_rich_edit_exists' => true, + 'wp_roles' => true, + 'wp_scripts' => true, + 'wp_settings_errors' => true, + 'wp_settings_fields' => true, + 'wp_settings_sections' => true, + 'wp_smiliessearch' => true, + 'wp_styles' => true, + 'wp_taxonomies' => true, + 'wp_the_query' => true, + 'wp_theme_directories' => true, + 'wp_themes' => true, + 'wp_user_roles' => true, + 'wp_version' => true, + 'wp_widget_factory' => true, + 'wp_xmlrpc_server' => true, + 'wpcommentsjavascript' => true, + 'wpcommentspopupfile' => true, + 'wpdb' => true, + 'wpsmiliestrans' => true, + 'year' => true, + ); + + /** + * Retrieve a list with the names of global WP variables. + * + * @since 3.0.0 + * + * @return array Array with the variables names as keys. The value is irrelevant. + */ + public static function get_names() { + return self::$wp_globals; + } + + /** + * Verify if a given variable name is the name of a WP global variable. + * + * @since 3.0.0 + * + * @param string $name The full variable name with or without leading dollar sign. + * This allows for passing an array key variable name, such as + * `'_GET'` retrieved from `$GLOBALS['_GET']`. + * > Note: when passing an array key, string quotes are expected + * to have been stripped already. + * + * @return bool + */ + public static function is_wp_global( $name ) { + if ( strpos( $name, '$' ) === 0 ) { + $name = substr( $name, 1 ); + } + + return isset( self::$wp_globals[ $name ] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPHookHelper.php b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPHookHelper.php new file mode 100644 index 00000000..b5b4f4a1 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Helpers/WPHookHelper.php @@ -0,0 +1,113 @@ +> Function name as key, array with target + * parameter position and name(s) as value. + */ + private static $hookInvokeFunctions = array( + 'do_action' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + 'do_action_ref_array' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + 'do_action_deprecated' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + 'apply_filters' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + 'apply_filters_ref_array' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + 'apply_filters_deprecated' => array( + 'position' => 1, + 'name' => 'hook_name', + ), + ); + + /** + * Retrieve a list of the WordPress functions which invoke hooks. + * + * @since 3.0.0 + * + * @param bool $include_deprecated Whether to include the names of functions + * which are used to invoke deprecated hooks. + * Defaults to `true`. + * + * @return array Array with the function names as keys. The value is irrelevant. + */ + public static function get_functions( $include_deprecated = true ) { + $hooks = array_fill_keys( array_keys( self::$hookInvokeFunctions ), true ); + if ( false === $include_deprecated ) { + unset( + $hooks['do_action_deprecated'], + $hooks['apply_filters_deprecated'] + ); + } + + return $hooks; + } + + /** + * Retrieve the parameter information for the hook name parameter from a stack of parameters + * passed to one of the WP hook functions. + * + * @since 3.0.0 + * + * @param string $function_name The name of the WP hook function which the parameters were passed to. + * @param array $parameters The output of a previous call to PassedParameters::getParameters(). + * + * @return array|false Array with information on the parameter at the specified offset, + * or with the specified name. + * Or `FALSE` if the specified parameter is not found. + * See the PHPCSUtils PassedParameters::getParameters() documentation + * for the format of the returned (single-dimensional) array. + */ + public static function get_hook_name_param( $function_name, array $parameters ) { + $function_lc = strtolower( $function_name ); + if ( isset( self::$hookInvokeFunctions[ $function_lc ] ) === false ) { + return false; + } + + return PassedParameters::getParameterFromStack( + $parameters, + self::$hookInvokeFunctions[ $function_lc ]['position'], + self::$hookInvokeFunctions[ $function_lc ]['name'] + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniff.php new file mode 100644 index 00000000..eaa7c22b --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniff.php @@ -0,0 +1,72 @@ +phpcsFile = $phpcsFile; + $this->tokens = $phpcsFile->getTokens(); + + return $this->process_token( $stackPtr ); + } + + /** + * Processes a sniff when one of its tokens is encountered. + * + * @since 0.11.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + abstract public function process_token( $stackPtr ); +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php new file mode 100644 index 00000000..de35bdff --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php @@ -0,0 +1,252 @@ +tokens[ $stackPtr ]['code'] ] ) + && Arrays::isShortArray( $this->phpcsFile, $stackPtr ) === false + ) { + // Short list, not short array. + return; + } + + /* + * Determine the array opener & closer. + */ + $array_open_close = Arrays::getOpenClose( $this->phpcsFile, $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + unset( $array_open_close ); + + // Pass off to either the single line or multi-line array analysis. + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + $this->process_single_line_array( $stackPtr, $opener, $closer ); + } else { + $this->process_multi_line_array( $stackPtr, $opener ); + } + } + + /** + * Check that associative arrays are always multi-line. + * + * @since 0.13.0 The actual checks contained in this method used to + * be in the `process()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return void + */ + protected function process_single_line_array( $stackPtr, $opener, $closer ) { + $array_items = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + if ( ( false === $this->allow_single_item_single_line_associative_arrays + && empty( $array_items ) ) + || ( true === $this->allow_single_item_single_line_associative_arrays + && \count( $array_items ) === 1 ) + ) { + return; + } + + /* + * Make sure the double arrow is for *this* array, not for a nested one. + */ + $array_has_keys = false; + foreach ( $array_items as $item ) { + if ( Arrays::getDoubleArrowPtr( $this->phpcsFile, $item['start'], $item['end'] ) !== false ) { + $array_has_keys = true; + break; + } + } + + if ( false === $array_has_keys ) { + return; + } + $error = 'When an array uses associative keys, each value should start on %s.'; + if ( true === $this->allow_single_item_single_line_associative_arrays ) { + $error = 'When a multi-item array uses associative keys, each value should start on %s.'; + } + + /* + * Just add a new line before the array closer. + * The multi-line array fixer will then fix the individual array items in the next fixer loop. + */ + SpacesFixer::checkAndFix( + $this->phpcsFile, + $closer, + $this->phpcsFile->findPrevious( \T_WHITESPACE, ( $closer - 1 ), null, true ), + 'newline', + $error, + 'AssociativeArrayFound', + 'error' + ); + } + + /** + * Process a multi-line array. + * + * @since 0.13.0 The actual checks contained in this method used to + * be in the `ArrayDeclaration` sniff. + * @since 3.0.0 Removed the `$closer` parameter. + * + * @param int $stackPtr The position of the current token in the stack. + * @param int $opener The position of the array opener. + * + * @return void + */ + protected function process_multi_line_array( $stackPtr, $opener ) { + /* + * Check that each array item starts on a new line. + */ + $array_items = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + $end_of_last_item = $opener; + + foreach ( $array_items as $item ) { + $end_of_this_item = ( $item['end'] + 1 ); + + // Find the line on which the item starts. + $first_content = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ), + $item['start'], + $end_of_this_item, + true + ); + + // Ignore comments after array items if the next real content starts on a new line. + if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_last_item ]['line'] + && ( \T_COMMENT === $this->tokens[ $first_content ]['code'] + || isset( Tokens::$phpcsCommentTokens[ $this->tokens[ $first_content ]['code'] ] ) ) + ) { + $end_of_comment = $first_content; + + // Find the end of (multi-line) /* */- style trailing comments. + if ( substr( ltrim( $this->tokens[ $end_of_comment ]['content'] ), 0, 2 ) === '/*' ) { + while ( ( \T_COMMENT === $this->tokens[ $end_of_comment ]['code'] + || isset( Tokens::$phpcsCommentTokens[ $this->tokens[ $end_of_comment ]['code'] ] ) ) + && substr( rtrim( $this->tokens[ $end_of_comment ]['content'] ), -2 ) !== '*/' + && ( $end_of_comment + 1 ) < $end_of_this_item + ) { + ++$end_of_comment; + } + + if ( $this->tokens[ $end_of_comment ]['line'] !== $this->tokens[ $end_of_last_item ]['line'] ) { + // Multi-line trailing comment. + $end_of_last_item = $end_of_comment; + } + } + + $next = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ), + ( $end_of_comment + 1 ), + $end_of_this_item, + true + ); + + if ( false === $next ) { + // Shouldn't happen, but just in case. + $end_of_last_item = $end_of_this_item; // @codeCoverageIgnore + continue; // @codeCoverageIgnore + } + + if ( $this->tokens[ $next ]['line'] !== $this->tokens[ $first_content ]['line'] ) { + $first_content = $next; + } + } + + if ( false === $first_content ) { + // Shouldn't happen, but just in case. + $end_of_last_item = $end_of_this_item; // @codeCoverageIgnore + continue; // @codeCoverageIgnore + } + + if ( $this->tokens[ $end_of_last_item ]['line'] === $this->tokens[ $first_content ]['line'] ) { + SpacesFixer::checkAndFix( + $this->phpcsFile, + $first_content, + $end_of_last_item, + 'newline', + 'Each item in a multi-line array must be on %s. Found: %s', + 'ArrayItemNoNewLine', + 'error' + ); + } + + $end_of_last_item = $end_of_this_item; + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php new file mode 100644 index 00000000..76bcda60 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php @@ -0,0 +1,550 @@ +ignore_tokens = Tokens::$heredocTokens; + unset( $this->ignore_tokens[ \T_START_HEREDOC ], $this->ignore_tokens[ \T_START_NOWDOC ] ); + $this->ignore_tokens[ \T_INLINE_HTML ] = \T_INLINE_HTML; + + return Collections::arrayOpenTokensBC(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + if ( ! isset( $this->tab_width ) ) { + $this->tab_width = Helper::getTabWidth( $this->phpcsFile ); + } + + if ( isset( Collections::shortArrayListOpenTokensBC()[ $this->tokens[ $stackPtr ]['code'] ] ) + && Arrays::isShortArray( $this->phpcsFile, $stackPtr ) === false + ) { + // Short list, not short array. + return; + } + + /* + * Determine the array opener & closer. + */ + $array_open_close = Arrays::getOpenClose( $this->phpcsFile, $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + // Not interested in single line arrays. + return; + } + + /* + * Check the closing bracket is lined up with the start of the content on the line + * containing the array opener. + */ + $opener_line_spaces = $this->get_indentation_size( $opener ); + $closer_line_spaces = ( $this->tokens[ $closer ]['column'] - 1 ); + + if ( $closer_line_spaces !== $opener_line_spaces ) { + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; + $error_code = 'CloseBraceNotAligned'; + + /* + * Report & fix the issue if the close brace is on its own line with + * nothing or only indentation whitespace before it. + */ + if ( 0 === $closer_line_spaces + || ( \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code'] + && 1 === $this->tokens[ ( $closer - 1 ) ]['column'] ) + ) { + $this->add_array_alignment_error( + $closer, + $error, + $error_code, + $opener_line_spaces, + $closer_line_spaces, + $this->get_indentation_string( $opener_line_spaces ) + ); + } else { + /* + * Otherwise, only report the error, don't try and fix it (yet). + * + * It will get corrected in a future loop of the fixer once the closer + * has been moved to its own line by the `ArrayDeclarationSpacing` sniff. + */ + $this->phpcsFile->addError( + $error, + $closer, + $error_code, + array( $opener_line_spaces, $closer_line_spaces ) + ); + } + + unset( $error, $error_code ); + } + + /* + * Verify & correct the array item indentation. + */ + $array_items = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + if ( empty( $array_items ) ) { + // Strange, no array items found. + return; + } + + $expected_spaces = ( $opener_line_spaces + $this->tab_width ); + $expected_indent = $this->get_indentation_string( $expected_spaces ); + $end_of_previous_item = $opener; + + foreach ( $array_items as $item ) { + $end_of_this_item = ( $item['end'] + 1 ); + + // Find the line on which the item starts. + $first_content = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ), + $item['start'], + $end_of_this_item, + true + ); + + // Deal with trailing comments. + if ( false !== $first_content + && \T_COMMENT === $this->tokens[ $first_content ]['code'] + && $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line'] + ) { + $first_content = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE, \T_COMMENT ), + ( $first_content + 1 ), + $end_of_this_item, + true + ); + } + + if ( false === $first_content ) { + $end_of_previous_item = $end_of_this_item; + continue; + } + + // Bow out from reporting and fixing mixed multi-line/single-line arrays. + // That is handled by the ArrayDeclarationSpacingSniff. + if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line'] ) { + return $closer; + } + + // Ignore this item if there is anything but whitespace before the start of the next item. + if ( 1 !== $this->tokens[ $first_content ]['column'] ) { + // Go to the start of the line. + $i = $first_content; + while ( 1 !== $this->tokens[ --$i ]['column'] ); + + if ( \T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + $end_of_previous_item = $end_of_this_item; + continue; + } + } + + $found_spaces = ( $this->tokens[ $first_content ]['column'] - 1 ); + + if ( $found_spaces !== $expected_spaces ) { + $this->add_array_alignment_error( + $first_content, + 'Array item not aligned correctly; expected %s spaces but found %s', + 'ItemNotAligned', + $expected_spaces, + $found_spaces, + $expected_indent + ); + } + + // No need for further checking if this is a one-line array item. + if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + $end_of_previous_item = $end_of_this_item; + continue; + } + + /* + * Multi-line array items. + * + * Verify & if needed, correct the indentation of subsequent lines. + * Subsequent lines may be indented more or less than the mimimum expected indent, + * but the "first line after" should be indented - at least - as much as the very first line + * of the array item. + * Indentation correction for subsequent lines will be based on that diff. + */ + + // Find first token on second line of the array item. + // If the second line is a heredoc/nowdoc, continue on until we find a line with a different token. + // Same for the second line of a multi-line text string. + for ( $ptr = ( $first_content + 1 ); $ptr <= $item['end']; $ptr++ ) { + if ( $this->tokens[ $first_content ]['line'] !== $this->tokens[ $ptr ]['line'] + && 1 === $this->tokens[ $ptr ]['column'] + && false === $this->ignore_token( $ptr ) + ) { + break; + } + } + + $first_content_on_line2 = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ), + $ptr, + $end_of_this_item, + true + ); + + if ( false === $first_content_on_line2 ) { + /* + * Apparently there were only tokens in the ignore list on subsequent lines. + * + * In that case, the comma after the array item might be on a line by itself, + * so check its placement. + */ + if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line'] + && \T_COMMA === $this->tokens[ $end_of_this_item ]['code'] + && ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces + ) { + $this->add_array_alignment_error( + $end_of_this_item, + 'Comma after multi-line array item not aligned correctly; expected %s spaces, but found %s', + 'MultiLineArrayItemCommaNotAligned', + $expected_spaces, + ( $this->tokens[ $end_of_this_item ]['column'] - 1 ), + $expected_indent + ); + } + + $end_of_previous_item = $end_of_this_item; + continue; + } + + $found_spaces_on_line2 = $this->get_indentation_size( $first_content_on_line2 ); + $expected_spaces_on_line2 = $expected_spaces; + + if ( $found_spaces < $found_spaces_on_line2 ) { + $expected_spaces_on_line2 += ( $found_spaces_on_line2 - $found_spaces ); + } + + if ( $found_spaces_on_line2 !== $expected_spaces_on_line2 ) { + + $fix = $this->phpcsFile->addFixableError( + 'Multi-line array item not aligned correctly; expected %s spaces, but found %s', + $first_content_on_line2, + 'MultiLineArrayItemNotAligned', + array( + $expected_spaces_on_line2, + $found_spaces_on_line2, + ) + ); + + if ( true === $fix ) { + $expected_indent_on_line2 = $this->get_indentation_string( $expected_spaces_on_line2 ); + + $this->phpcsFile->fixer->beginChangeset(); + + // Fix second line for the array item. + if ( 1 === $this->tokens[ $first_content_on_line2 ]['column'] + && \T_COMMENT === $this->tokens[ $first_content_on_line2 ]['code'] + ) { + $actual_comment = ltrim( $this->tokens[ $first_content_on_line2 ]['content'] ); + $replacement = $expected_indent_on_line2 . $actual_comment; + + $this->phpcsFile->fixer->replaceToken( $first_content_on_line2, $replacement ); + + } else { + $this->fix_alignment_error( $first_content_on_line2, $expected_indent_on_line2 ); + } + + // Fix subsequent lines. + for ( $i = ( $first_content_on_line2 + 1 ); $i <= $item['end']; $i++ ) { + // We're only interested in the first token on each line. + if ( 1 !== $this->tokens[ $i ]['column'] ) { + if ( $this->tokens[ $i ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + // We might as well quit if we're past the first token on the last line. + break; + } + continue; + } + + $first_content_on_line = $this->phpcsFile->findNext( + array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ), + $i, + $end_of_this_item, + true + ); + + if ( false === $first_content_on_line ) { + break; + } + + // Ignore lines with heredoc and nowdoc tokens and subsequent lines in multi-line strings. + if ( true === $this->ignore_token( $first_content_on_line ) ) { + $i = $first_content_on_line; + continue; + } + + $found_spaces_on_line = $this->get_indentation_size( $first_content_on_line ); + $expected_spaces_on_line = ( $expected_spaces_on_line2 + ( $found_spaces_on_line - $found_spaces_on_line2 ) ); + $expected_spaces_on_line = max( $expected_spaces_on_line, 0 ); // Can't be below 0. + $expected_indent_on_line = $this->get_indentation_string( $expected_spaces_on_line ); + + if ( $found_spaces_on_line !== $expected_spaces_on_line ) { + if ( 1 === $this->tokens[ $first_content_on_line ]['column'] + && \T_COMMENT === $this->tokens[ $first_content_on_line ]['code'] + ) { + $actual_comment = ltrim( $this->tokens[ $first_content_on_line ]['content'] ); + $replacement = $expected_indent_on_line . $actual_comment; + + $this->phpcsFile->fixer->replaceToken( $first_content_on_line, $replacement ); + } else { + $this->fix_alignment_error( $first_content_on_line, $expected_indent_on_line ); + } + } + + // Move past any potential empty lines between the previous non-empty line and this one. + // No need to do the fixes twice. + $i = $first_content_on_line; + } + + /* + * Check the placement of the comma after the array item as it might be on a line by itself. + */ + if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line'] + && \T_COMMA === $this->tokens[ $end_of_this_item ]['code'] + && ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces + ) { + $this->add_array_alignment_error( + $end_of_this_item, + 'Comma after array item not aligned correctly; expected %s spaces, but found %s', + 'MultiLineArrayItemCommaNotAligned', + $expected_spaces, + ( $this->tokens[ $end_of_this_item ]['column'] - 1 ), + $expected_indent + ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + } + + $end_of_previous_item = $end_of_this_item; + } + } + + /** + * Should the token be ignored ? + * + * This method is only intended to be used with the first token on a line + * for subsequent lines in an multi-line array item. + * + * @param int $ptr Stack pointer to the first token on a line. + * + * @return bool + */ + protected function ignore_token( $ptr ) { + $token_code = $this->tokens[ $ptr ]['code']; + + if ( isset( $this->ignore_tokens[ $token_code ] ) ) { + return true; + } + + /* + * If it's a subsequent line of a multi-line sting, it will not start with a quote + * character, nor just *be* a quote character. + */ + if ( isset( Tokens::$stringTokens[ $token_code ] ) === true ) { + // Deal with closing quote of a multi-line string being on its own line. + if ( "'" === $this->tokens[ $ptr ]['content'] + || '"' === $this->tokens[ $ptr ]['content'] + ) { + return true; + } + + // Deal with subsequent lines of a multi-line string where the token is broken up per line. + if ( "'" !== $this->tokens[ $ptr ]['content'][0] + && '"' !== $this->tokens[ $ptr ]['content'][0] + ) { + return true; + } + } + + return false; + } + + /** + * Determine the line indentation whitespace. + * + * @param int $ptr Stack pointer to an arbitrary token on a line. + * + * @return int Nr of spaces found. Where necessary, tabs are translated to spaces. + */ + protected function get_indentation_size( $ptr ) { + + // Find the first token on the line. + for ( ; $ptr >= 0; $ptr-- ) { + if ( 1 === $this->tokens[ $ptr ]['column'] ) { + break; + } + } + + $whitespace = ''; + + if ( \T_WHITESPACE === $this->tokens[ $ptr ]['code'] + || \T_DOC_COMMENT_WHITESPACE === $this->tokens[ $ptr ]['code'] + ) { + return $this->tokens[ $ptr ]['length']; + } + + /* + * Special case for multi-line, non-docblock comments. + * Only applicable for subsequent lines in an array item. + * + * First/Single line is tokenized as T_WHITESPACE + T_COMMENT + * Subsequent lines are tokenized as T_COMMENT including the indentation whitespace. + */ + if ( \T_COMMENT === $this->tokens[ $ptr ]['code'] ) { + $content = $this->tokens[ $ptr ]['content']; + $actual_comment = ltrim( $content ); + $whitespace = str_replace( $actual_comment, '', $content ); + } + + return \strlen( $whitespace ); + } + + /** + * Create an indentation string. + * + * @param int $nr Number of spaces the indentation should be. + * + * @return string + */ + protected function get_indentation_string( $nr ) { + if ( 0 >= $nr ) { + return ''; + } + + // Space-based indentation. + if ( false === $this->tabIndent ) { + return str_repeat( ' ', $nr ); + } + + // Tab-based indentation. + $num_tabs = (int) floor( $nr / $this->tab_width ); + $remaining = ( $nr % $this->tab_width ); + $tab_indent = str_repeat( "\t", $num_tabs ); + $tab_indent .= str_repeat( ' ', $remaining ); + + return $tab_indent; + } + + /** + * Throw an error and fix incorrect array alignment. + * + * @param int $ptr Stack pointer to the first content on the line. + * @param string $error Error message. + * @param string $error_code Error code. + * @param int $expected Expected nr of spaces (tabs translated to space value). + * @param int $found Found nr of spaces (tabs translated to space value). + * @param string $new_indent Whitespace indent replacement content. + * + * @return void + */ + protected function add_array_alignment_error( $ptr, $error, $error_code, $expected, $found, $new_indent ) { + + $fix = $this->phpcsFile->addFixableError( $error, $ptr, $error_code, array( $expected, $found ) ); + if ( true === $fix ) { + $this->fix_alignment_error( $ptr, $new_indent ); + } + } + + /** + * Fix incorrect array alignment. + * + * @param int $ptr Stack pointer to the first content on the line. + * @param string $new_indent Whitespace indent replacement content. + * + * @return void + */ + protected function fix_alignment_error( $ptr, $new_indent ) { + if ( 1 === $this->tokens[ $ptr ]['column'] ) { + $this->phpcsFile->fixer->addContentBefore( $ptr, $new_indent ); + } else { + $this->phpcsFile->fixer->replaceToken( ( $ptr - 1 ), $new_indent ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php new file mode 100644 index 00000000..b32fb26a --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php @@ -0,0 +1,174 @@ +tokens[ $stackPtr ]; + if ( ! isset( $token['bracket_closer'] ) ) { + return; + } + + /* + * Handle square brackets without a key (array assignments) first. + */ + $first_non_ws = $this->phpcsFile->findNext( \T_WHITESPACE, ( $stackPtr + 1 ), null, true ); + if ( $first_non_ws === $token['bracket_closer'] ) { + $error = 'There should be %1$s between the square brackets for an array assignment without an explicit key. Found: %2$s'; + SpacesFixer::checkAndFix( + $this->phpcsFile, + $stackPtr, + $token['bracket_closer'], + 0, + $error, + 'SpacesBetweenBrackets' + ); + + return; + } + + /* + * Handle the spaces around explicit array keys. + */ + $needs_spaces = true; + + // Skip over a potential plus/minus sign for integers. + $first_effective = $first_non_ws; + if ( \T_MINUS === $this->tokens[ $first_effective ]['code'] || \T_PLUS === $this->tokens[ $first_effective ]['code'] ) { + $first_effective = $this->phpcsFile->findNext( \T_WHITESPACE, ( $first_effective + 1 ), null, true ); + } + + $next_non_ws = $this->phpcsFile->findNext( \T_WHITESPACE, ( $first_effective + 1 ), null, true ); + if ( ( \T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $first_effective ]['code'] + || \T_LNUMBER === $this->tokens[ $first_effective ]['code'] ) + && $next_non_ws === $token['bracket_closer'] + ) { + $needs_spaces = false; + } + + $has_space_after_opener = ( \T_WHITESPACE === $this->tokens[ ( $stackPtr + 1 ) ]['code'] ); + $has_space_before_close = ( \T_WHITESPACE === $this->tokens[ ( $token['bracket_closer'] - 1 ) ]['code'] ); + + // The array key should be surrounded by spaces unless the key only consists of a string or an integer. + if ( true === $needs_spaces + && ( false === $has_space_after_opener || false === $has_space_before_close ) + ) { + $error = 'Array keys must be surrounded by spaces unless they contain a string or an integer.'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpacesAroundArrayKeys' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + if ( false === $has_space_after_opener ) { + $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); + } + + if ( false === $has_space_before_close ) { + $this->phpcsFile->fixer->addContentBefore( $token['bracket_closer'], ' ' ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + } elseif ( false === $needs_spaces && ( $has_space_after_opener || $has_space_before_close ) ) { + $error = 'Array keys must NOT be surrounded by spaces if they only contain a string or an integer.'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpacesAroundArrayKeys' ); + if ( true === $fix ) { + if ( $has_space_after_opener ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $i = ( $stackPtr + 1 ); $i < $token['bracket_closer']; $i++ ) { + if ( \T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + break; + } + + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + + if ( $has_space_before_close ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $i = ( $token['bracket_closer'] - 1 ); $i > $stackPtr; $i-- ) { + if ( \T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + break; + } + + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + } + } + + // If spaces are needed, check that there is only one space. + if ( true === $needs_spaces ) { + if ( $has_space_after_opener ) { + $error = 'There should be exactly %1$s before the array key. Found: %2$s'; + SpacesFixer::checkAndFix( + $this->phpcsFile, + $stackPtr, + $first_non_ws, + 1, + $error, + 'TooMuchSpaceBeforeKey' + ); + } + + if ( $has_space_before_close ) { + $last_non_ws = $this->phpcsFile->findPrevious( \T_WHITESPACE, ( $token['bracket_closer'] - 1 ), null, true ); + $error = 'There should be exactly %1$s after the array key. Found: %2$s'; + SpacesFixer::checkAndFix( + $this->phpcsFile, + $last_non_ws, + $token['bracket_closer'], + 1, + $error, + 'TooMuchSpaceAfterKey' + ); + } + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php new file mode 100644 index 00000000..87630c86 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php @@ -0,0 +1,583 @@ += 60, align at column 60. + * - for the outliers, i.e. the array indexes where the end position + * goes past column 60, it will not align the arrow, the sniff will + * just make sure there is only one space between the end of the + * array index and the double arrow. + * + * The column value is regarded as a hard value, i.e. includes indentation, + * so setting it very low is not a good idea. + * + * @since 0.14.0 + * + * @var int + */ + public $maxColumn = 1000; + + /** + * Whether or not to align the arrow operator for multi-line array items. + * + * Whether or not an item is regarded as multi-line is based on the **value** + * of the item, not the key. + * + * Valid values are: + * - 'always': Default. Align all arrays items regardless of single/multi-line. + * - 'never': Never align array items which span multiple lines. + * This will enforce one space between the array index and the + * double arrow operator for multi-line array items, independently + * of the alignment of the rest of the array items. + * Multi-line items where the arrow is already aligned with the + * "expected" alignment, however, will be left alone. + * - operator : Only align the operator for multi-line arrays items if the + * + number percentage of multi-line items passes the comparison. + * - As it is a percentage, the number has to be between 0 and 100. + * - Supported operators: <, <=, >, >=, ==, =, !=, <> + * - The percentage is calculated against all array items + * (with and without assignment operator). + * - The (new) expected alignment will be calculated based only + * on the items being aligned. + * - Multi-line items where the arrow is already aligned with the + * (new) "expected" alignment, however, will be left alone. + * Examples: + * * Setting this to `!=100` or `<100` means that alignment will + * be enforced, unless *all* array items are multi-line. + * This is probably the most commonly desired situation. + * * Setting this to `=100` means that alignment will only + * be enforced, if *all* array items are multi-line. + * * Setting this to `<50` means that the majority of array items + * need to be single line before alignment is enforced for + * multi-line items in the array. + * * Setting this to `=0` is useless as in that case there are + * no multi-line items in the array anyway. + * + * This setting will respect the `ignoreNewlines` and `maxColumnn` settings. + * + * @since 0.14.0 + * + * @var string|int + */ + public $alignMultilineItems = 'always'; + + /** + * Storage for parsed $alignMultilineItems operator part. + * + * @since 0.14.0 + * + * @var string + */ + private $operator; + + /** + * Storage for parsed $alignMultilineItems numeric part. + * + * Stored as a string as the comparison will be done string based. + * + * @since 0.14.0 + * + * @var string + */ + private $number; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.14.0 + * + * @return array + */ + public function register() { + return Collections::arrayOpenTokensBC(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + if ( isset( Collections::shortArrayListOpenTokensBC()[ $this->tokens[ $stackPtr ]['code'] ] ) + && Arrays::isShortArray( $this->phpcsFile, $stackPtr ) === false + ) { + // Short list, not short array. + return; + } + + /* + * Determine the array opener & closer. + */ + $array_open_close = Arrays::getOpenClose( $this->phpcsFile, $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + + $array_items = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + if ( empty( $array_items ) ) { + return; + } + + // Pass off to either the single line or multi-line array analysis. + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + return $this->process_single_line_array( $stackPtr, $array_items, $opener, $closer ); + } else { + return $this->process_multi_line_array( $stackPtr, $array_items, $opener, $closer ); + } + } + + /** + * Process a single-line array. + * + * While the WP standard does not allow single line multi-item associative arrays, + * this sniff should function independently of that. + * + * The `WordPress.WhiteSpace.OperatorSpacing` sniff already covers checking that + * there is a space between the array key and the double arrow, but doesn't + * enforce it to be exactly one space for single line arrays. + * That is what this method covers. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param array $items Info array containing information on each array item. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_single_line_array( $stackPtr, $items, $opener, $closer ) { + /* + * For single line arrays, we don't care about what level the arrow is from. + * Just find and fix them all. + */ + $next_arrow = $this->phpcsFile->findNext( + \T_DOUBLE_ARROW, + ( $opener + 1 ), + $closer + ); + + while ( false !== $next_arrow ) { + if ( \T_WHITESPACE === $this->tokens[ ( $next_arrow - 1 ) ]['code'] ) { + $space_length = $this->tokens[ ( $next_arrow - 1 ) ]['length']; + if ( 1 !== $space_length ) { + $error = 'Expected 1 space between "%s" and double arrow; %s found'; + $data = array( + $this->tokens[ ( $next_arrow - 2 ) ]['content'], + $space_length, + ); + + $fix = $this->phpcsFile->addFixableWarning( $error, $next_arrow, 'SpaceBeforeDoubleArrow', $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $next_arrow - 1 ), ' ' ); + } + } + } + + // Find the position of the next double arrow. + $next_arrow = $this->phpcsFile->findNext( + \T_DOUBLE_ARROW, + ( $next_arrow + 1 ), + $closer + ); + } + + // Ignore any child-arrays as the double arrows in these will already have been handled. + return ( $closer + 1 ); + } + + /** + * Process a multi-line array. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param array $items Info array containing information on each array item. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return void + */ + protected function process_multi_line_array( $stackPtr, $items, $opener, $closer ) { + + $this->maxColumn = (int) $this->maxColumn; + $this->validate_align_multiline_items(); + + /* + * Determine what the spacing before the arrow should be. + * + * Will unset any array items without double arrow and with new line whitespace + * if newlines are to be ignored, so the second foreach loop only has to deal + * with items which need attention. + * + * This sniff does not take incorrect indentation of array keys into account. + * That's for the `WordPress.Arrays.ArrayIndentation` sniff to fix. + * If that would affect the alignment, a second (or third) loop of the fixer + * will correct it (again) after the indentation has been fixed. + */ + $index_end_cols = array(); // Keep track of the end column position of index keys. + $double_arrow_cols = array(); // Keep track of arrow column position and count. + $multi_line_count = 0; + $total_items = \count( $items ); + + foreach ( $items as $key => $item ) { + // Find the double arrow if there is one. + $double_arrow = Arrays::getDoubleArrowPtr( $this->phpcsFile, $item['start'], $item['end'] ); + if ( false === $double_arrow ) { + unset( $items[ $key ] ); + continue; + } + + // Find the end of the array key. + $last_index_token = $this->phpcsFile->findPrevious( + \T_WHITESPACE, + ( $double_arrow - 1 ), + $item['start'], + true + ); + + if ( true === $this->ignoreNewlines + && $this->tokens[ $last_index_token ]['line'] !== $this->tokens[ $double_arrow ]['line'] + ) { + // Ignore this item as it has a new line between the item key and the double arrow. + unset( $items[ $key ] ); + continue; + } + + $index_end_position = ( $this->tokens[ $last_index_token ]['column'] + ( $this->tokens[ $last_index_token ]['length'] - 1 ) ); + $items[ $key ]['operatorPtr'] = $double_arrow; + $items[ $key ]['last_index_token'] = $last_index_token; + $items[ $key ]['last_index_col'] = $index_end_position; + + if ( $this->tokens[ $last_index_token ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + $items[ $key ]['single_line'] = true; + } else { + $items[ $key ]['single_line'] = false; + ++$multi_line_count; + } + + if ( ( $index_end_position + 2 ) <= $this->maxColumn ) { + $index_end_cols[] = $index_end_position; + } + + if ( ! isset( $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] ) ) { + $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] = 1; + } else { + ++$double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ]; + } + } + unset( $key, $item, $double_arrow, $last_index_token ); + + if ( empty( $items ) || empty( $index_end_cols ) ) { + // No actionable array items found. + return; + } + + /* + * Determine whether the operators for multi-line items should be aligned. + */ + if ( 'always' === $this->alignMultilineItems ) { + $alignMultilineItems = true; + } elseif ( 'never' === $this->alignMultilineItems ) { + $alignMultilineItems = false; + } else { + $percentage = (string) round( ( $multi_line_count / $total_items ) * 100, 0 ); + + // Bit hacky, but this is the only comparison function in PHP which allows to + // pass the comparison operator. And hey, it works ;-). + $alignMultilineItems = version_compare( $percentage, $this->number, $this->operator ); + } + + /* + * If necessary, rebuild the $index_end_cols and $double_arrow_cols arrays + * excluding multi-line items. + */ + if ( false === $alignMultilineItems ) { + $select_index_end_cols = array(); + $double_arrow_cols = array(); + + foreach ( $items as $item ) { + if ( false === $item['single_line'] ) { + continue; + } + + if ( ( $item['last_index_col'] + 2 ) <= $this->maxColumn ) { + $select_index_end_cols[] = $item['last_index_col']; + } + + if ( ! isset( $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] ) ) { + $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] = 1; + } else { + ++$double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ]; + } + } + } + + /* + * Determine the expected position of the double arrows. + */ + if ( ! empty( $select_index_end_cols ) ) { + $max_index_width = max( $select_index_end_cols ); + } else { + $max_index_width = max( $index_end_cols ); + } + + $expected_col = ( $max_index_width + 2 ); + + if ( false === $this->exact && ! empty( $double_arrow_cols ) ) { + /* + * If the alignment does not have to be exact, see if a majority + * group of the arrows is already at an acceptable position. + */ + arsort( $double_arrow_cols, \SORT_NUMERIC ); + reset( $double_arrow_cols ); + $count = current( $double_arrow_cols ); + + if ( $count > 1 || ( 1 === $count && \count( $items ) === 1 ) ) { + // Allow for several groups of arrows having the same $count. + $filtered_double_arrow_cols = array_keys( $double_arrow_cols, $count, true ); + + foreach ( $filtered_double_arrow_cols as $col ) { + if ( $col > $expected_col && $col <= $this->maxColumn ) { + $expected_col = $col; + break; + } + } + } + } + unset( $max_index_width, $count, $filtered_double_arrow_cols, $col ); + + /* + * Verify and correct the spacing around the double arrows. + */ + foreach ( $items as $item ) { + if ( $this->tokens[ $item['operatorPtr'] ]['column'] === $expected_col + && $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line'] + ) { + // Already correctly aligned. + continue; + } + + if ( \T_WHITESPACE !== $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['code'] ) { + $before = 0; + } elseif ( $this->tokens[ $item['last_index_token'] ]['line'] !== $this->tokens[ $item['operatorPtr'] ]['line'] ) { + $before = 'newline'; + } else { + $before = $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['length']; + } + + /* + * Deal with index sizes larger than maxColumn and with multi-line + * array items which should not be aligned. + */ + if ( ( $item['last_index_col'] + 2 ) > $this->maxColumn + || ( false === $alignMultilineItems && false === $item['single_line'] ) + ) { + + if ( ( $item['last_index_col'] + 2 ) === $this->tokens[ $item['operatorPtr'] ]['column'] + && $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line'] + ) { + // MaxColumn/Multi-line item exception, already correctly aligned. + continue; + } + + $prefix = 'LongIndex'; + if ( false === $alignMultilineItems && false === $item['single_line'] ) { + $prefix = 'MultilineItem'; + } + + $error_code = $prefix . 'SpaceBeforeDoubleArrow'; + if ( 0 === $before ) { + $error_code = $prefix . 'NoSpaceBeforeDoubleArrow'; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Expected 1 space between "%s" and double arrow; %s found.', + $item['operatorPtr'], + $error_code, + array( + $this->tokens[ $item['last_index_token'] ]['content'], + $before, + ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + // Remove whitespace tokens between the end of the index and the arrow, if any. + for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + // Add the correct whitespace. + $this->phpcsFile->fixer->addContent( $item['last_index_token'], ' ' ); + + $this->phpcsFile->fixer->endChangeset(); + } + continue; + } + + /* + * Deal with the space before double arrows in all other cases. + */ + $expected_whitespace = $expected_col - ( $this->tokens[ $item['last_index_token'] ]['column'] + $this->tokens[ $item['last_index_token'] ]['length'] ); + + $fix = $this->phpcsFile->addFixableWarning( + 'Array double arrow not aligned correctly; expected %s space(s) between "%s" and double arrow, but found %s.', + $item['operatorPtr'], + 'DoubleArrowNotAligned', + array( + $expected_whitespace, + $this->tokens[ $item['last_index_token'] ]['content'], + $before, + ) + ); + + if ( true === $fix ) { + if ( 0 === $before || 'newline' === $before ) { + $this->phpcsFile->fixer->beginChangeset(); + + // Remove whitespace tokens between the end of the index and the arrow, if any. + for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + // Add the correct whitespace. + $this->phpcsFile->fixer->addContent( + $item['last_index_token'], + str_repeat( ' ', $expected_whitespace ) + ); + + $this->phpcsFile->fixer->endChangeset(); + } elseif ( $expected_whitespace > $before ) { + // Add to the existing whitespace to prevent replacing tabs with spaces. + // That's the concern of another sniff. + $this->phpcsFile->fixer->addContent( + ( $item['operatorPtr'] - 1 ), + str_repeat( ' ', ( $expected_whitespace - $before ) ) + ); + } else { + // Too much whitespace found. + $this->phpcsFile->fixer->replaceToken( + ( $item['operatorPtr'] - 1 ), + str_repeat( ' ', $expected_whitespace ) + ); + } + } + } + } + + /** + * Validate that a valid value has been received for the alignMultilineItems property. + * + * This message may be thrown more than once if the property is being changed inline in a file. + * + * @since 0.14.0 + * + * @return void + */ + protected function validate_align_multiline_items() { + $alignMultilineItems = $this->alignMultilineItems; + + if ( 'always' === $alignMultilineItems || 'never' === $alignMultilineItems ) { + return; + } else { + // Correct for a potentially added % sign. + $alignMultilineItems = rtrim( $alignMultilineItems, '%' ); + + if ( preg_match( '`^([=<>!]{1,2})(100|[0-9]{1,2})$`', $alignMultilineItems, $matches ) > 0 ) { + $operator = $matches[1]; + $number = (int) $matches[2]; + + if ( \in_array( $operator, array( '<', '<=', '>', '>=', '==', '=', '!=', '<>' ), true ) === true + && ( $number >= 0 && $number <= 100 ) + ) { + $this->alignMultilineItems = $alignMultilineItems; + $this->number = (string) $number; + $this->operator = $operator; + return; + } + } + } + + $this->phpcsFile->addError( + 'Invalid property value passed: "%s". The value for the "alignMultilineItems" property for the "WordPress.Arrays.MultipleStatementAlignment" sniff should be either "always", "never" or an comparison operator + a number between 0 and 100.', + 0, + 'InvalidPropertyPassed', + array( $this->alignMultilineItems ) + ); + + // Reset to the default if an invalid value was received. + $this->alignMultilineItems = 'always'; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/AssignmentInTernaryConditionSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/AssignmentInTernaryConditionSniff.php new file mode 100644 index 00000000..a74b5f29 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/AssignmentInTernaryConditionSniff.php @@ -0,0 +1,173 @@ +assignment_tokens = Tokens::$assignmentTokens; + unset( $this->assignment_tokens[ \T_DOUBLE_ARROW ] ); + + $starters = Tokens::$booleanOperators; + $starters[ \T_SEMICOLON ] = \T_SEMICOLON; + $starters[ \T_OPEN_PARENTHESIS ] = \T_OPEN_PARENTHESIS; + $starters[ \T_INLINE_ELSE ] = \T_INLINE_ELSE; + + $this->condition_start_tokens = $starters; + + return array( + \T_INLINE_THEN, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + $token = $this->tokens[ $stackPtr ]; + + // Check if the condition for the ternary is bracketed. + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $prev ]['code'] ) { + if ( ! isset( $this->tokens[ $prev ]['parenthesis_opener'] ) ) { + return; + } + + $opener = $this->tokens[ $prev ]['parenthesis_opener']; + $closer = $prev; + } elseif ( isset( $token['nested_parenthesis'] ) ) { + $opener = Parentheses::getLastOpener( $this->phpcsFile, $stackPtr ); + $closer = Parentheses::getLastCloser( $this->phpcsFile, $stackPtr ); + + $next_statement_closer = BCFile::findEndOfStatement( $this->phpcsFile, $stackPtr, array( \T_COLON, \T_CLOSE_PARENTHESIS, \T_CLOSE_SQUARE_BRACKET ) ); + if ( false !== $next_statement_closer && $next_statement_closer < $closer ) { + // Parentheses are unrelated to the ternary. + return; + } + + $prev_statement_closer = BCFile::findStartOfStatement( $this->phpcsFile, $stackPtr, array( \T_COLON, \T_OPEN_PARENTHESIS, \T_OPEN_SQUARE_BRACKET ) ); + if ( false !== $prev_statement_closer && $opener < $prev_statement_closer ) { + // Parentheses are unrelated to the ternary. + return; + } + + if ( $closer > $stackPtr ) { + $closer = $stackPtr; + } + } else { + // No parenthesis found, can't determine where the conditional part of the ternary starts. + return; + } + + $startPos = $opener; + + do { + $hasAssignment = $this->phpcsFile->findNext( $this->assignment_tokens, ( $startPos + 1 ), $closer ); + if ( false === $hasAssignment ) { + return; + } + + // Examine whether the left side is a variable. + $hasVariable = false; + $conditionStart = $startPos; + $altConditionStart = $this->phpcsFile->findPrevious( + $this->condition_start_tokens, + ( $hasAssignment - 1 ), + $startPos + ); + if ( false !== $altConditionStart ) { + $conditionStart = $altConditionStart; + } + + for ( $i = $hasAssignment; $i > $conditionStart; $i-- ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // If this is a variable or array, we've seen all we need to see. + if ( \T_VARIABLE === $this->tokens[ $i ]['code'] + || \T_CLOSE_SQUARE_BRACKET === $this->tokens[ $i ]['code'] + ) { + $hasVariable = true; + break; + } + + // If this is a function call or something, we are OK. + if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] ) { + break; + } + } + + if ( true === $hasVariable ) { + $this->phpcsFile->addWarning( + 'Variable assignment found within a condition. Did you mean to do a comparison?', + $hasAssignment, + 'FoundInTernaryCondition' + ); + } + + $startPos = $hasAssignment; + + } while ( $startPos < $closer ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/EscapedNotTranslatedSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/EscapedNotTranslatedSniff.php new file mode 100644 index 00000000..45084498 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/CodeAnalysis/EscapedNotTranslatedSniff.php @@ -0,0 +1,89 @@ + Key is the name of the function being matched, value the alternative to use. + */ + protected $target_functions = array( + 'esc_html' => 'esc_html__', + 'esc_attr' => 'esc_attr__', + ); + + /** + * Process the parameters of a matched function. + * + * @since 2.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + if ( \count( $parameters ) === 1 ) { + return; + } + + /* + * We already know that there will be a valid open+close parenthesis, otherwise the sniff + * would have bowed out long before. + */ + $opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + $closer = $this->tokens[ $opener ]['parenthesis_closer']; + + $data = array( + $matched_content, + $this->target_functions[ $matched_content ], + GetTokensAsString::compact( $this->phpcsFile, $stackPtr, $closer, true ), + ); + + $this->phpcsFile->addWarning( + '%s() expects only a $text parameter. Did you mean to use %s() ? Found: %s', + $stackPtr, + 'Found', + $data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php new file mode 100644 index 00000000..590f6478 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php @@ -0,0 +1,301 @@ + array(), + 'cacheset' => array(), + 'cachedelete' => array(), + ); + + /** + * A list of functions that get data from the cache. + * + * @since 0.6.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 Moved from the generic `Sniff` class to this class. + * + * @var array + */ + protected $cacheGetFunctions = array( + 'wp_cache_get' => true, + ); + + /** + * A list of functions that set data in the cache. + * + * @since 0.6.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 Moved from the generic `Sniff` class to this class. + * + * @var array + */ + protected $cacheSetFunctions = array( + 'wp_cache_set' => true, + 'wp_cache_add' => true, + ); + + /** + * A list of functions that delete data from the cache. + * + * @since 0.6.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 Moved from the generic `Sniff` class to this class. + * + * @var array + */ + protected $cacheDeleteFunctions = array( + 'wp_cache_delete' => true, + 'clean_attachment_cache' => true, + 'clean_blog_cache' => true, + 'clean_bookmark_cache' => true, + 'clean_category_cache' => true, + 'clean_comment_cache' => true, + 'clean_network_cache' => true, + 'clean_object_term_cache' => true, + 'clean_page_cache' => true, + 'clean_post_cache' => true, + 'clean_term_cache' => true, + 'clean_user_cache' => true, + ); + + /** + * The lists of $wpdb methods. + * + * @since 0.6.0 + * @since 0.11.0 Changed from static to non-static. + * + * @var array[] + */ + protected $methods = array( + 'cachable' => array( + 'delete' => true, + 'get_var' => true, + 'get_col' => true, + 'get_row' => true, + 'get_results' => true, + 'query' => true, + 'replace' => true, + 'update' => true, + ), + 'noncachable' => array( + 'insert' => true, + ), + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + \T_VARIABLE, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + // Check for $wpdb variable. + if ( '$wpdb' !== $this->tokens[ $stackPtr ]['content'] ) { + return; + } + + $is_object_call = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false === $is_object_call + || ( \T_OBJECT_OPERATOR !== $this->tokens[ $is_object_call ]['code'] + && \T_NULLSAFE_OBJECT_OPERATOR !== $this->tokens[ $is_object_call ]['code'] ) + ) { + // This is not a call to the wpdb object. + return; + } + + $methodPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $is_object_call + 1 ), null, true ); + $method = strtolower( $this->tokens[ $methodPtr ]['content'] ); + + $this->mergeFunctionLists(); + + if ( ! isset( $this->methods['all'][ $method ] ) ) { + return; + } + + $endOfStatement = $this->phpcsFile->findNext( array( \T_SEMICOLON, \T_CLOSE_TAG ), ( $stackPtr + 1 ) ); + if ( false === $endOfStatement ) { + return; + } + + // Check for Database Schema Changes/ table truncation. + for ( $_pos = ( $stackPtr + 1 ); $_pos < $endOfStatement; $_pos++ ) { + $_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $_pos, $endOfStatement ); + if ( false === $_pos ) { + break; + } + + if ( strpos( strtoupper( TextStrings::stripQuotes( $this->tokens[ $_pos ]['content'] ) ), 'TRUNCATE ' ) === 0 ) { + // Ignore queries to truncate the database as caching those is irrelevant and they need a direct db query. + return; + } + + if ( preg_match( '#\b(?:ALTER|CREATE|DROP)\b#i', $this->tokens[ $_pos ]['content'] ) > 0 ) { + $this->phpcsFile->addWarning( 'Attempting a database schema change is discouraged.', $_pos, 'SchemaChange' ); + } + } + + $this->phpcsFile->addWarning( 'Use of a direct database call is discouraged.', $stackPtr, 'DirectQuery' ); + + if ( ! isset( $this->methods['cachable'][ $method ] ) ) { + return $endOfStatement; + } + + $cached = false; + $wp_cache_get = false; + + $scope_function = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( false !== $scope_function ) { + $scopeStart = $this->tokens[ $scope_function ]['scope_opener']; + $scopeEnd = $this->tokens[ $scope_function ]['scope_closer']; + + for ( $i = ( $scopeStart + 1 ); $i < $scopeEnd; $i++ ) { + if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + + if ( isset( $this->cacheDeleteFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + + if ( \in_array( $method, array( 'query', 'update', 'replace', 'delete' ), true ) ) { + $cached = true; + break; + } + } elseif ( isset( $this->cacheGetFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + + $wp_cache_get = true; + + } elseif ( isset( $this->cacheSetFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + + if ( $wp_cache_get ) { + $cached = true; + break; + } + } + } + } + } + + if ( ! $cached ) { + $message = 'Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().'; + $this->phpcsFile->addWarning( $message, $stackPtr, 'NoCaching' ); + } + + return $endOfStatement; + } + + /** + * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. + * + * @since 0.11.0 Split out from the `process()` method. + * + * @return void + */ + protected function mergeFunctionLists() { + if ( ! isset( $this->methods['all'] ) ) { + $this->methods['all'] = array_merge( $this->methods['cachable'], $this->methods['noncachable'] ); + } + + if ( $this->customCacheGetFunctions !== $this->addedCustomFunctions['cacheget'] ) { + $this->cacheGetFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customCacheGetFunctions, + $this->cacheGetFunctions + ); + + $this->addedCustomFunctions['cacheget'] = $this->customCacheGetFunctions; + } + + if ( $this->customCacheSetFunctions !== $this->addedCustomFunctions['cacheset'] ) { + $this->cacheSetFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customCacheSetFunctions, + $this->cacheSetFunctions + ); + + $this->addedCustomFunctions['cacheset'] = $this->customCacheSetFunctions; + } + + if ( $this->customCacheDeleteFunctions !== $this->addedCustomFunctions['cachedelete'] ) { + $this->cacheDeleteFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customCacheDeleteFunctions, + $this->cacheDeleteFunctions + ); + + $this->addedCustomFunctions['cachedelete'] = $this->customCacheDeleteFunctions; + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php new file mode 100644 index 00000000..043175d8 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php @@ -0,0 +1,761 @@ +prepare method. + * + * Checks the following issues: + * - The only placeholders supported are: %d, %f (%F), %s, %i, and their variations. + * - Literal % signs need to be properly escaped as `%%`. + * - Simple placeholders (%d, %f, %F, %s, %i) should be left unquoted in the query string. + * - Complex placeholders - numbered and formatted variants - will not be quoted + * automagically by $wpdb->prepare(), so if used for values, should be quoted in + * the query string. + * The only exception to this is complex placeholders for %i. In that case, the + * replacement *will* still be backtick-quoted. + * - Either an array of replacements should be passed matching the number of + * placeholders found or individual parameters for each placeholder should + * be passed. + * - Wildcards for LIKE compare values should be passed in via a replacement parameter. + * + * The sniff allows for a specific pattern with a variable number of placeholders + * created using code along the lines of: + * `sprintf( 'query .... IN (%s) ...', implode( ',', array_fill( 0, count( $something ), '%s' ) ) )`. + * + * @link https://developer.wordpress.org/reference/classes/wpdb/prepare/ + * @link https://core.trac.wordpress.org/changeset/41496 + * @link https://core.trac.wordpress.org/changeset/41471 + * @link https://core.trac.wordpress.org/changeset/55151 + * + * @since 0.14.0 + * @since 3.0.0 Support for the %i placeholder has been added + * + * @uses \WordPressCS\WordPress\Helpers\MinimumWPVersionTrait::$minimum_wp_version + */ +final class PreparedSQLPlaceholdersSniff extends Sniff { + + use MinimumWPVersionTrait; + use WPDBTrait; + + /** + * These regexes were originally copied from https://www.php.net/function.sprintf#93552 + * and adjusted for limitations in `$wpdb->prepare()`. + * + * Near duplicate of the one used in the WP.I18n sniff, but with fewer types allowed. + * + * Note: The regex delimiters and modifiers are not included to allow this regex to be + * concatenated together with other regex partials. + * + * @since 0.14.0 + * + * @var string + */ + const PREPARE_PLACEHOLDER_REGEX = '(?: + (? true, + ); + + /** + * Storage for the stack pointer to the method call token. + * + * @since 0.14.0 + * + * @var int + */ + protected $methodPtr; + + /** + * Simple regex snippet to recognize and remember quotes. + * + * @since 0.14.0 + * + * @var string + */ + private $regex_quote = '["\']'; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.14.0 + * + * @return array + */ + public function register() { + return array( + \T_VARIABLE, + \T_STRING, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + $this->set_minimum_wp_version(); + + if ( ! $this->is_wpdb_method_call( $this->phpcsFile, $stackPtr, $this->target_methods ) ) { + return; + } + + $parameters = PassedParameters::getParameters( $this->phpcsFile, $this->methodPtr ); + if ( empty( $parameters ) ) { + return; + } + + $query = PassedParameters::getParameterFromStack( $parameters, 1, 'query' ); + if ( false === $query ) { + return; + } + + $text_string_tokens_found = false; + $variable_found = false; + $sql_wildcard_found = false; + $total_placeholders = 0; + $total_parameters = \count( $parameters ); + $valid_in_clauses = array( + 'uses_in' => 0, + 'implode_fill' => 0, + 'adjustment_count' => 0, + ); + $skip_from = null; + $skip_to = null; + + for ( $i = $query['start']; $i <= $query['end']; $i++ ) { + // Skip over groups of tokens if they are part of an inline function call. + if ( isset( $skip_from, $skip_to ) && $i >= $skip_from && $i <= $skip_to ) { + $i = $skip_to; + continue; + } + + if ( ! isset( Tokens::$textStringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + if ( \T_VARIABLE === $this->tokens[ $i ]['code'] ) { + if ( '$wpdb' !== $this->tokens[ $i ]['content'] ) { + $variable_found = true; + } + continue; + } + + // Detect a specific pattern for variable replacements in combination with `IN`. + if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + + if ( 'sprintf' === strtolower( $this->tokens[ $i ]['content'] ) ) { + $sprintf_parameters = PassedParameters::getParameters( $this->phpcsFile, $i ); + + if ( ! empty( $sprintf_parameters ) ) { + /* + * Check for named params. sprintf() does not support this due to its variadic nature, + * and we cannot analyse the code correctly if it is used, so skip the whole sprintf() + * in that case. + */ + $valid_sprintf = true; + foreach ( $sprintf_parameters as $param ) { + if ( isset( $param['name'] ) ) { + $valid_sprintf = false; + break; + } + } + + if ( false === $valid_sprintf ) { + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] + && isset( $this->tokens[ $next ]['parenthesis_closer'] ) + ) { + $skip_from = ( $i + 1 ); + $skip_to = $this->tokens[ $next ]['parenthesis_closer']; + } + + continue; + } + + // We know for sure this sprintf() uses positional parameters, so this will be fine. + $skip_from = ( $sprintf_parameters[1]['end'] + 1 ); + $last_param = end( $sprintf_parameters ); + $skip_to = ( $last_param['end'] + 1 ); + + $valid_in_clauses['implode_fill'] += $this->analyse_sprintf( $sprintf_parameters ); + $valid_in_clauses['adjustment_count'] += ( \count( $sprintf_parameters ) - 1 ); + } + unset( $sprintf_parameters, $valid_sprintf, $last_param ); + + } elseif ( 'implode' === strtolower( $this->tokens[ $i ]['content'] ) ) { + $prev = $this->phpcsFile->findPrevious( + Tokens::$emptyTokens + array( \T_STRING_CONCAT => \T_STRING_CONCAT ), + ( $i - 1 ), + $query['start'], + true + ); + + if ( isset( Tokens::$textStringTokens[ $this->tokens[ $prev ]['code'] ] ) ) { + $prev_content = TextStrings::stripQuotes( $this->tokens[ $prev ]['content'] ); + $regex_quote = $this->get_regex_quote_snippet( $prev_content, $this->tokens[ $prev ]['content'] ); + + // Only examine the implode if preceded by an ` IN (`. + if ( preg_match( '`\s+IN\s*\(\s*(' . $regex_quote . ')?$`i', $prev_content, $match ) > 0 ) { + + if ( isset( $match[1] ) && $regex_quote !== $this->regex_quote ) { + $this->phpcsFile->addError( + 'Dynamic placeholder generation should not have surrounding quotes.', + $prev, + 'QuotedDynamicPlaceholderGeneration' + ); + } + + if ( $this->analyse_implode( $i ) === true ) { + ++$valid_in_clauses['uses_in']; + ++$valid_in_clauses['implode_fill']; + + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] + && isset( $this->tokens[ $next ]['parenthesis_closer'] ) + ) { + $skip_from = ( $i + 1 ); + $skip_to = $this->tokens[ $next ]['parenthesis_closer']; + } + } + } + unset( $next, $prev_content, $regex_quote, $match ); + } + unset( $prev ); + } + } + + continue; + } + + $text_string_tokens_found = true; + $content = $this->tokens[ $i ]['content']; + + $regex_quote = $this->regex_quote; + if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + $content = TextStrings::stripQuotes( $content ); + $regex_quote = $this->get_regex_quote_snippet( $content, $this->tokens[ $i ]['content'] ); + } + + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] + || \T_HEREDOC === $this->tokens[ $i ]['code'] + ) { + // Only interested in actual query text, so strip out variables. + $stripped_content = TextStrings::stripEmbeds( $content ); + if ( $stripped_content !== $content ) { + $vars_without_wpdb = array_filter( + TextStrings::getEmbeds( $content ), + static function ( $symbol ) { + return preg_match( '`^\{?\$\{?wpdb\??->`', $symbol ) !== 1; + } + ); + + $content = $stripped_content; + + if ( ! empty( $vars_without_wpdb ) ) { + $variable_found = true; + } + } + unset( $stripped_content, $vars_without_wpdb ); + } + + $placeholders = preg_match_all( '`' . self::PREPARE_PLACEHOLDER_REGEX . '`x', $content, $matches ); + if ( $placeholders > 0 ) { + $total_placeholders += $placeholders; + } + + /* + * Analyse the query for incorrect LIKE queries. + * + * - `LIKE %s` is the only correct one. + * - `LIKE '%s'` or `LIKE "%s"` will not be reported here, but in the quote check. + * - Any other `LIKE` statement should be reported, either for using `LIKE` without + * SQL wildcards or for not passing the SQL wildcards via the replacement. + */ + $regex = '`\s+LIKE\s*(?:(' . $regex_quote . ')(?!%s(?:\1|$))(?P.*?)(?:\1|$)|(?:concat\((?![^\)]*%s[^\)]*\))(?P[^\)]*))\))`i'; + if ( preg_match_all( $regex, $content, $matches ) > 0 ) { + $walk = array(); + if ( ! empty( $matches['content'] ) ) { + $matches['content'] = array_filter( $matches['content'] ); + if ( ! empty( $matches['content'] ) ) { + $walk[] = 'content'; + } + } + if ( ! empty( $matches['concat'] ) ) { + $matches['concat'] = array_filter( $matches['concat'] ); + if ( ! empty( $matches['concat'] ) ) { + $walk[] = 'concat'; + } + } + + if ( ! empty( $walk ) ) { + foreach ( $walk as $match_key ) { + foreach ( $matches[ $match_key ] as $index => $match ) { + $data = array( $matches[0][ $index ] ); + + // Both a `%` as well as a `_` are wildcards in SQL. + if ( strpos( $match, '%' ) === false && strpos( $match, '_' ) === false ) { + $this->phpcsFile->addWarning( + 'Unless you are using SQL wildcards, using LIKE is inefficient. Use a straight compare instead. Found: %s.', + $i, + 'LikeWithoutWildcards', + $data + ); + } else { + $sql_wildcard_found = true; + + if ( strpos( $match, '%s' ) === false ) { + $this->phpcsFile->addError( + 'SQL wildcards for a LIKE query should be passed in through a replacement parameter. Found: %s.', + $i, + 'LikeWildcardsInQuery', + $data + ); + } else { + $this->phpcsFile->addError( + 'SQL wildcards for a LIKE query should be passed in through a replacement parameter and the variable part of the replacement should be escaped using "esc_like()". Found: %s.', + $i, + 'LikeWildcardsInQueryWithPlaceholder', + $data + ); + } + } + + /* + * Don't throw `UnescapedLiteral`, `UnsupportedPlaceholder` or `QuotedPlaceholder` + * for this part of the SQL query. + */ + $content = preg_replace( '`' . preg_quote( $match, '`' ) . '`', '', $content, 1 ); + } + } + } + unset( $matches, $index, $match, $data ); + } + + if ( strpos( $content, '%' ) === false ) { + continue; + } + + /* + * Analyse the query for unsupported placeholders. + */ + if ( preg_match_all( self::UNSUPPORTED_PLACEHOLDER_REGEX, $content, $matches ) > 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + if ( '%' === $match ) { + $this->phpcsFile->addError( + 'Found unescaped literal "%%" character.', + $i, + 'UnescapedLiteral', + array( $match ) + ); + } else { + $this->phpcsFile->addError( + 'Unsupported placeholder used in $wpdb->prepare(). Found: "%s".', + $i, + 'UnsupportedPlaceholder', + array( $match ) + ); + } + } + } + unset( $match, $matches ); + } + + if ( $this->wp_version_compare( $this->minimum_wp_version, '6.2', '<' ) ) { + if ( preg_match_all( '`' . self::PREPARE_PLACEHOLDER_REGEX . '`x', $content, $matches ) > 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + if ( 'i' === substr( $match, -1 ) ) { + $this->phpcsFile->addError( + 'The %%i modifier is only supported in WP 6.2 or higher. Found: "%s".', + $i, + 'UnsupportedIdentifierPlaceholder', + array( $match ) + ); + } + } + } + } + unset( $match, $matches ); + } + + /* + * Analyse the query for single/double quoted simple value placeholders + * Identifiers are checked separately. + */ + $regex = '`(' . $regex_quote . ')%[dfFs]\1`'; + if ( preg_match_all( $regex, $content, $matches ) > 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'Simple placeholders should not be quoted in the query string in $wpdb->prepare(). Found: %s.', + $i, + 'QuotedSimplePlaceholder', + array( $match ) + ); + } + } + unset( $match, $matches ); + } + + /* + * Analyse the query for quoted identifier placeholders. + */ + $regex = '/(' . $regex_quote . '|`)(?' . self::PREPARE_PLACEHOLDER_REGEX . ')\1/x'; + if ( preg_match_all( $regex, $content, $matches ) > 0 ) { + if ( ! empty( $matches ) ) { + foreach ( $matches['placeholder'] as $index => $match ) { + if ( 'i' === substr( $match, -1 ) ) { + $this->phpcsFile->addError( + 'Placeholders used for identifiers (%%i) in the query string in $wpdb->prepare() are always quoted automagically. Please remove the surrounding quotes. Found: %s', + $i, + 'QuotedIdentifierPlaceholder', + array( $matches[0][ $index ] ) + ); + } + } + } + unset( $index, $match, $matches ); + } + + /* + * Analyse the query for unquoted complex placeholders. + */ + $regex = '`(? 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + if ( substr( $match, -1 ) !== 'i' && preg_match( '`^%[dfFsi]$`', $match ) !== 1 ) { // Identifiers must always be unquoted. + $this->phpcsFile->addWarning( + 'Complex placeholders used for values in the query string in $wpdb->prepare() will NOT be quoted automagically. Found: %s.', + $i, + 'UnquotedComplexPlaceholder', + array( $match ) + ); + } + } + } + unset( $match, $matches ); + } + + /* + * Check for an ` IN (%s)` clause. + */ + $found_in = preg_match_all( '`\s+IN\s*\(\s*%s\s*\)`i', $content, $matches ); + if ( $found_in > 0 ) { + $valid_in_clauses['uses_in'] += $found_in; + } + unset( $found_in ); + } + + if ( false === $text_string_tokens_found ) { + // Query string passed in as a variable or function call, nothing to examine. + return; + } + + if ( 0 === $total_placeholders ) { + if ( 1 === $total_parameters ) { + if ( false === $variable_found && false === $sql_wildcard_found ) { + /* + * Only throw this warning if the PreparedSQL sniff won't throw one about + * variables being found. + * Also don't throw it if we just advised to use a replacement variable to pass a + * string containing an SQL wildcard. + */ + $this->phpcsFile->addWarning( + 'It is not necessary to prepare a query which doesn\'t use variable replacement.', + $i, + 'UnnecessaryPrepare' + ); + } + } elseif ( 0 === $valid_in_clauses['uses_in'] ) { + $this->phpcsFile->addWarning( + 'Replacement variables found, but no valid placeholders found in the query.', + $i, + 'UnfinishedPrepare' + ); + } + + return; + } + + if ( 1 === $total_parameters ) { + $this->phpcsFile->addError( + 'Placeholders found in the query passed to $wpdb->prepare(), but no replacements found. Expected %d replacement(s) parameters.', + $stackPtr, + 'MissingReplacements', + array( $total_placeholders ) + ); + return; + } + + $replacements = $parameters; + unset( $replacements['query'], $replacements[1] ); // Remove the query param, whether passed positionally or named. + + // The parameters may have been passed as an array in the variadic $args parameter. + $args_param = PassedParameters::getParameterFromStack( $parameters, 2, 'args' ); + if ( false !== $args_param && 2 === $total_parameters ) { + $next = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $args_param['start'], + ( $args_param['end'] + 1 ), + true + ); + + if ( false !== $next + && ( \T_ARRAY === $this->tokens[ $next ]['code'] + || ( isset( Collections::shortArrayListOpenTokensBC()[ $this->tokens[ $next ]['code'] ] ) + && Arrays::isShortArray( $this->phpcsFile, $next ) === true ) ) + ) { + $replacements = PassedParameters::getParameters( $this->phpcsFile, $next ); + } + } + + $total_replacements = \count( $replacements ); + $total_placeholders -= $valid_in_clauses['adjustment_count']; + + // Bow out when `IN` clauses have been used which appear to be correct. + if ( $valid_in_clauses['uses_in'] > 0 + && $valid_in_clauses['uses_in'] === $valid_in_clauses['implode_fill'] + && 1 === $total_replacements + ) { + return; + } + + /* + * Verify that the correct amount of replacements have been passed. + */ + if ( $total_replacements !== $total_placeholders ) { + $this->phpcsFile->addWarning( + 'Incorrect number of replacements passed to $wpdb->prepare(). Found %d replacement parameters, expected %d.', + $stackPtr, + 'ReplacementsWrongNumber', + array( $total_replacements, $total_placeholders ) + ); + } + } + + /** + * Retrieve a regex snippet to recognize and remember quotes based on the quote style + * used in the original string (if any). + * + * This allows for recognizing `"` and `\'` in single quoted strings, + * recognizing `'` and `\"` in double quotes strings and `'` and `"`when the quote + * style is unknown or it is a non-quoted string (heredoc/nowdoc and such). + * + * @since 0.14.0 + * + * @param string $stripped_content Text string content without surrounding quotes. + * @param string $original_content Original content for the same text string. + * + * @return string + */ + protected function get_regex_quote_snippet( $stripped_content, $original_content ) { + $regex_quote = $this->regex_quote; + + if ( $original_content !== $stripped_content ) { + $quote_style = $original_content[0]; + + if ( '"' === $quote_style ) { + $regex_quote = '\\\\"|\''; + } elseif ( "'" === $quote_style ) { + $regex_quote = '"|\\\\\''; + } + } + + return $regex_quote; + } + + /** + * Analyse a sprintf() query wrapper to see if it contains a specific code pattern + * to deal correctly with `IN` queries. + * + * The pattern we are searching for is: + * `sprintf( 'query ....', implode( ',', array_fill( 0, count( $something ), '%s' ) ) )` + * + * @since 0.14.0 + * + * @param array $sprintf_params Parameters details for the sprintf call. + * + * @return int The number of times the pattern was found in the replacements. + */ + protected function analyse_sprintf( $sprintf_params ) { + $found = 0; + + unset( $sprintf_params[1] ); // Remove the positionally passed $format param. + + foreach ( $sprintf_params as $sprintf_param ) { + $implode = $this->phpcsFile->findNext( + Tokens::$emptyTokens + array( \T_NS_SEPARATOR => \T_NS_SEPARATOR ), + $sprintf_param['start'], + $sprintf_param['end'], + true + ); + if ( \T_STRING === $this->tokens[ $implode ]['code'] + && 'implode' === strtolower( $this->tokens[ $implode ]['content'] ) + ) { + if ( $this->analyse_implode( $implode ) === true ) { + ++$found; + } + } + } + + return $found; + } + + /** + * Analyse an implode() function call to see if it contains a specific code pattern + * to dynamically create placeholders. + * + * The pattern we are searching for is: + * `implode( ',', array_fill( 0, count( $something ), '%s' ) )` + * + * This pattern presumes unquoted placeholders! + * + * Identifiers (%i) are not supported, as this function is designed to work + * with `IN()`, which contains a list of values. In the future, it should + * be possible to simplify code using the implode/array_fill pattern to + * use a variable number of identifiers, e.g. `CONCAT(%...i)`, + * https://core.trac.wordpress.org/ticket/54042 + * + * @since 0.14.0 + * + * @param int $implode_token The stackPtr to the implode function call. + * + * @return bool True if the pattern is found, false otherwise. + */ + protected function analyse_implode( $implode_token ) { + $implode_params = PassedParameters::getParameters( $this->phpcsFile, $implode_token ); + if ( empty( $implode_params ) || \count( $implode_params ) !== 2 ) { + return false; + } + + $implode_separator_param = PassedParameters::getParameterFromStack( $implode_params, 1, 'separator' ); + if ( false === $implode_separator_param + || preg_match( '`^(["\']), ?\1$`', $implode_separator_param['clean'] ) !== 1 + ) { + return false; + } + + $implode_array_param = PassedParameters::getParameterFromStack( $implode_params, 2, 'array' ); + if ( false === $implode_array_param ) { + return false; + } + + $array_fill = $this->phpcsFile->findNext( + Tokens::$emptyTokens + array( \T_NS_SEPARATOR => \T_NS_SEPARATOR ), + $implode_array_param['start'], + $implode_array_param['end'], + true + ); + + if ( \T_STRING !== $this->tokens[ $array_fill ]['code'] + || 'array_fill' !== strtolower( $this->tokens[ $array_fill ]['content'] ) + ) { + return false; + } + + $array_fill_value_param = PassedParameters::getParameter( $this->phpcsFile, $array_fill, 3, 'value' ); + if ( false === $array_fill_value_param ) { + return false; + } + + if ( "'%i'" === $array_fill_value_param['clean'] + || '"%i"' === $array_fill_value_param['clean'] + ) { + $firstNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $array_fill_value_param['start'], $array_fill_value_param['end'], true ); + + $this->phpcsFile->addError( + 'The %i placeholder cannot be used within SQL `IN()` clauses.', + $firstNonEmpty, + 'IdentifierWithinIN' + ); + return false; + } + + return (bool) preg_match( '`^(["\'])%[dfFs]\1$`', $array_fill_value_param['clean'] ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLSniff.php new file mode 100644 index 00000000..7b6529ef --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/PreparedSQLSniff.php @@ -0,0 +1,242 @@ + + */ + protected $methods = array( + 'get_var' => true, + 'get_col' => true, + 'get_row' => true, + 'get_results' => true, + 'prepare' => true, + 'query' => true, + ); + + /** + * Functions that escape values for use in SQL queries. + * + * @since 0.9.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The property visibility has changed from `protected` to `private`. + * + * @var array + */ + private $SQLEscapingFunctions = array( + 'absint' => true, + 'esc_sql' => true, + 'floatval' => true, + 'intval' => true, + 'like_escape' => true, + ); + + /** + * Functions whose output is automatically escaped for use in SQL queries. + * + * @since 0.9.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 - Moved from the Sniff class to this class. + * - The property visibility has changed from `protected` to `private`. + * + * @var array + */ + private $SQLAutoEscapedFunctions = array( + 'count' => true, + ); + + /** + * Tokens that we don't flag when they are found in a $wpdb method call. + * + * This token array is augmented from within the register() method. + * + * @since 0.9.0 + * @since 3.0.0 The property visibility has changed from `protected` to `private`. + * + * @var array + */ + private $ignored_tokens = array( + \T_STRING_CONCAT => true, + \T_CONSTANT_ENCAPSED_STRING => true, + \T_COMMA => true, + \T_LNUMBER => true, + \T_DNUMBER => true, + \T_NS_SEPARATOR => true, + ); + + /** + * A loop pointer. + * + * It is a property so that we can access it in all of our methods. + * + * @since 0.9.0 + * + * @var int + */ + protected $i; + + /** + * The loop end marker. + * + * It is a property so that we can access it in all of our methods. + * + * @since 0.9.0 + * + * @var int + */ + protected $end; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.8.0 + * + * @return array + */ + public function register() { + // Enrich the array of tokens which can be safely ignored. + $this->ignored_tokens += Tokens::$bracketTokens; + $this->ignored_tokens += Tokens::$heredocTokens; + $this->ignored_tokens += Tokens::$castTokens; + $this->ignored_tokens += Tokens::$arithmeticTokens; + $this->ignored_tokens += Collections::incrementDecrementOperators(); + $this->ignored_tokens += Collections::objectOperators(); + $this->ignored_tokens += Tokens::$emptyTokens; + + // The contents of heredoc tokens needs to be examined. + unset( $this->ignored_tokens[ \T_HEREDOC ] ); + + return array( + \T_VARIABLE, + \T_STRING, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.8.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + if ( ! $this->is_wpdb_method_call( $this->phpcsFile, $stackPtr, $this->methods ) ) { + return; + } + + for ( $this->i; $this->i < $this->end; $this->i++ ) { + + if ( isset( $this->ignored_tokens[ $this->tokens[ $this->i ]['code'] ] ) ) { + continue; + } + + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $this->i ]['code'] + || \T_HEREDOC === $this->tokens[ $this->i ]['code'] + ) { + + $bad_variables = array_filter( + TextStrings::getEmbeds( $this->tokens[ $this->i ]['content'] ), + static function ( $symbol ) { + return preg_match( '`^\{?\$\{?wpdb\??->`', $symbol ) !== 1; + } + ); + + foreach ( $bad_variables as $bad_variable ) { + $this->phpcsFile->addError( + 'Use placeholders and $wpdb->prepare(); found interpolated variable %s at %s', + $this->i, + 'InterpolatedNotPrepared', + array( + $bad_variable, + $this->tokens[ $this->i ]['content'], + ) + ); + } + continue; + } + + if ( \T_VARIABLE === $this->tokens[ $this->i ]['code'] ) { + if ( '$wpdb' === $this->tokens[ $this->i ]['content'] ) { + $this->is_wpdb_method_call( $this->phpcsFile, $this->i, $this->methods ); + continue; + } + + if ( ContextHelper::is_safe_casted( $this->phpcsFile, $this->i ) ) { + continue; + } + } + + if ( \T_STRING === $this->tokens[ $this->i ]['code'] ) { + + if ( + isset( $this->SQLEscapingFunctions[ $this->tokens[ $this->i ]['content'] ] ) + || isset( $this->SQLAutoEscapedFunctions[ $this->tokens[ $this->i ]['content'] ] ) + ) { + + // Find the opening parenthesis. + $opening_paren = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $this->i + 1 ), null, true ); + + if ( false !== $opening_paren + && \T_OPEN_PARENTHESIS === $this->tokens[ $opening_paren ]['code'] + && isset( $this->tokens[ $opening_paren ]['parenthesis_closer'] ) + ) { + // Skip past to the end of the function call. + $this->i = $this->tokens[ $opening_paren ]['parenthesis_closer']; + continue; + } + } elseif ( FormattingFunctionsHelper::is_formatting_function( $this->tokens[ $this->i ]['content'] ) ) { + continue; + } + } + + $this->phpcsFile->addError( + 'Use placeholders and $wpdb->prepare(); found %s', + $this->i, + 'NotPrepared', + array( $this->tokens[ $this->i ]['content'] ) + ); + } + + return $this->end; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedClassesSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedClassesSniff.php new file mode 100644 index 00000000..4262889a --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedClassesSniff.php @@ -0,0 +1,57 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Avoid direct calls to the database.', + * 'classes' => array( 'PDO', '\Namespace\Classname' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + + 'mysql' => array( + 'type' => 'error', + 'message' => 'Accessing the database directly should be avoided. Please use the $wpdb object and associated functions instead. Found: %s.', + 'classes' => array( + 'mysqli', + 'PDO', + 'PDOStatement', + ), + ), + + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php new file mode 100644 index 00000000..7d23adaa --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php @@ -0,0 +1,63 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + + 'mysql' => array( + 'type' => 'error', + 'message' => 'Accessing the database directly should be avoided. Please use the $wpdb object and associated functions instead. Found: %s.', + 'functions' => array( + 'mysql_*', + 'mysqli_*', + 'mysqlnd_ms_*', + 'mysqlnd_qc_*', + 'mysqlnd_uh_*', + 'mysqlnd_memcache_*', + 'maxdb_*', + ), + 'allow' => array( + 'mysql_to_rfc3339' => true, + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/SlowDBQuerySniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/SlowDBQuerySniff.php new file mode 100644 index 00000000..5cbd99ac --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DB/SlowDBQuerySniff.php @@ -0,0 +1,58 @@ + array( + 'type' => 'warning', + 'message' => 'Detected usage of %s, possible slow query.', + 'keys' => array( + 'tax_query', + 'meta_query', + 'meta_key', + 'meta_value', + ), + ), + ); + } + + /** + * Callback to process each confirmed key, to check value. + * + * @param string $key Array index / key. + * @param mixed $val Assigned value. + * @param int $line Token line. + * @param array $group Group definition. + * + * @return bool Always returns TRUE as the value is irrelevant. + */ + public function callback( $key, $val, $line, $group ) { + return true; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php new file mode 100644 index 00000000..34bfe514 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php @@ -0,0 +1,168 @@ + Key is function name, value irrelevant. + */ + protected $target_functions = array( + 'current_time' => true, + ); + + /** + * Process the parameters of a matched function. + * + * @since 2.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + /* + * We already know there will be valid open & close parentheses as otherwise the parameter + * retrieval function call would have returned an empty array, so no additional checks needed. + */ + $open_parens = $this->phpcsFile->findNext( \T_OPEN_PARENTHESIS, $stackPtr ); + $close_parens = $this->tokens[ $open_parens ]['parenthesis_closer']; + + /* + * Check whether the first parameter is a timestamp format. + */ + $type_param = PassedParameters::getParameterFromStack( $parameters, 1, 'type' ); + if ( false === $type_param ) { + // Type parameter not found. Bow out. + return; + } + + $content_type = ''; + for ( $i = $type_param['start']; $i <= $type_param['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + if ( isset( Tokens::$textStringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + $content_type = trim( TextStrings::stripQuotes( $this->tokens[ $i ]['content'] ) ); + if ( 'U' !== $content_type && 'timestamp' !== $content_type ) { + // Most likely valid use of current_time(). + return; + } + + continue; + } + + if ( isset( Tokens::$heredocTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + /* + * If we're still here, we've encountered an unexpected token, like a variable or + * function call. Bow out as we can't determine the runtime value. + */ + return; + } + + $gmt_true = false; + + /* + * Check whether the second parameter, $gmt, is a set to `true` or `1`. + */ + $gmt_param = PassedParameters::getParameterFromStack( $parameters, 2, 'gmt' ); + if ( is_array( $gmt_param ) ) { + $content_gmt = ''; + if ( 'true' === $gmt_param['clean'] || '1' === $gmt_param['clean'] ) { + $content_gmt = $gmt_param['clean']; + $gmt_true = true; + } + } + + /* + * Non-UTC timestamp requested. + */ + if ( false === $gmt_true ) { + $this->phpcsFile->addWarning( + 'Calling current_time() with a $type of "timestamp" or "U" is strongly discouraged as it will not return a Unix (UTC) timestamp. Please consider using a non-timestamp format or otherwise refactoring this code.', + $stackPtr, + 'Requested' + ); + + return; + } + + /* + * UTC timestamp requested. Should use time() instead. + */ + $has_comment = $this->phpcsFile->findNext( Tokens::$commentTokens, ( $stackPtr + 1 ), ( $close_parens + 1 ) ); + $error = 'Don\'t use current_time() for retrieving a Unix (UTC) timestamp. Use time() instead. Found: %s'; + $error_code = 'RequestedUTC'; + + $code_snippet = "current_time( '" . $content_type . "'"; + if ( isset( $content_gmt ) ) { + $code_snippet .= ', ' . $content_gmt; + } + $code_snippet .= ' )'; + + if ( false !== $has_comment ) { + // If there are comments, we don't auto-fix as it would remove those comments. + $this->phpcsFile->addError( $error, $stackPtr, $error_code, array( $code_snippet ) ); + return; + } + + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, $error_code, array( $code_snippet ) ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $i = ( $stackPtr + 1 ); $i < $close_parens; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->replaceToken( $stackPtr, 'time(' ); + $this->phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php new file mode 100644 index 00000000..279da573 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php @@ -0,0 +1,59 @@ + array( + 'type' => 'error', + 'message' => 'Using %s() and similar isn\'t allowed, instead use WP internal timezone support.', + 'functions' => array( + 'date_default_timezone_set', + ), + ), + + /* + * Use gmdate(), not date(). + * Don't rely on the current PHP time zone as it might have been changed by third party code. + * + * @link https://make.wordpress.org/core/2019/09/23/date-time-improvements-wp-5-3/ + * @link https://core.trac.wordpress.org/ticket/46438 + * @link https://github.com/WordPress/WordPress-Coding-Standards/issues/1713 + */ + 'date' => array( + 'type' => 'error', + 'message' => '%s() is affected by runtime timezone changes which can cause date/time to be incorrectly displayed. Use gmdate() instead.', + 'functions' => array( + 'date', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Files/FileNameSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Files/FileNameSniff.php new file mode 100644 index 00000000..63fc9174 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Files/FileNameSniff.php @@ -0,0 +1,313 @@ + true, + 'class.wp-scripts.php' => true, + 'class.wp-styles.php' => true, + 'functions.wp-scripts.php' => true, + 'functions.wp-styles.php' => true, + ); + + /** + * Unit test version of the historical exceptions in WP core. + * + * @since 0.11.0 + * @since 3.0.0 Property has been renamed from `$unittest_class_exceptions` to `$unittest_hyphenation_exceptions`, + * + * @var array + */ + private $unittest_hyphenation_exceptions = array( + 'class.wp-dependencies.inc' => true, + 'class.wp-scripts.inc' => true, + 'class.wp-styles.inc' => true, + 'functions.wp-scripts.inc' => true, + 'functions.wp-styles.inc' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + if ( \defined( '\PHP_CODESNIFFER_IN_TESTS' ) ) { + $this->hyphenation_exceptions += $this->unittest_hyphenation_exceptions; + } + + return Collections::phpOpenTags(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + // Usage of `stripQuotes` is to ensure `stdin_path` passed by IDEs does not include quotes. + $file = TextStrings::stripQuotes( $this->phpcsFile->getFileName() ); + if ( 'STDIN' === $file ) { + return; + } + + $class_ptr = $this->phpcsFile->findNext( \T_CLASS, $stackPtr ); + if ( false !== $class_ptr && $this->is_test_class( $this->phpcsFile, $class_ptr ) ) { + /* + * This rule should not be applied to test classes (at all). + * @link https://github.com/WordPress/WordPress-Coding-Standards/issues/1995 + */ + return; + } + + // Respect phpcs:disable comments as long as they are not accompanied by an enable. + $i = -1; + while ( $i = $this->phpcsFile->findNext( \T_PHPCS_DISABLE, ( $i + 1 ) ) ) { + if ( empty( $this->tokens[ $i ]['sniffCodes'] ) + || isset( $this->tokens[ $i ]['sniffCodes']['WordPress'] ) + || isset( $this->tokens[ $i ]['sniffCodes']['WordPress.Files'] ) + || isset( $this->tokens[ $i ]['sniffCodes']['WordPress.Files.FileName'] ) + ) { + do { + $i = $this->phpcsFile->findNext( \T_PHPCS_ENABLE, ( $i + 1 ) ); + } while ( false !== $i + && ! empty( $this->tokens[ $i ]['sniffCodes'] ) + && ! isset( $this->tokens[ $i ]['sniffCodes']['WordPress'] ) + && ! isset( $this->tokens[ $i ]['sniffCodes']['WordPress.Files'] ) + && ! isset( $this->tokens[ $i ]['sniffCodes']['WordPress.Files.FileName'] ) ); + + if ( false === $i ) { + // The entire (rest of the) file is disabled. + return; + } + } + } + + $file_name = basename( $file ); + + $this->check_filename_is_hyphenated( $file_name ); + + if ( true === $this->strict_class_file_names && false !== $class_ptr ) { + $this->check_filename_has_class_prefix( $class_ptr, $file_name ); + } + + if ( false !== strpos( $file, \DIRECTORY_SEPARATOR . 'wp-includes' . \DIRECTORY_SEPARATOR ) + && false === $class_ptr + ) { + $this->check_filename_for_template_suffix( $stackPtr, $file_name ); + } + + // Only run this sniff once per file, no need to run it again. + return ( $this->phpcsFile->numTokens + 1 ); + } + + /** + * Generic check for lowercase hyphenated file names. + * + * @since 3.0.0 + * + * @param string $file_name The name of the current file. + * + * @return void + */ + protected function check_filename_is_hyphenated( $file_name ) { + $extension = strrchr( $file_name, '.' ); + $name = substr( $file_name, 0, ( strlen( $file_name ) - strlen( $extension ) ) ); + + $expected = strtolower( preg_replace( '`[[:punct:]]`', '-', $name ) ) . $extension; + if ( $file_name === $expected + || isset( $this->hyphenation_exceptions[ $file_name ] ) + ) { + return; + } + + if ( true === $this->is_theme && 1 === preg_match( self::THEME_EXCEPTIONS_REGEX, $file_name ) ) { + return; + } + + $this->phpcsFile->addError( + 'Filenames should be all lowercase with hyphens as word separators. Expected %s, but found %s.', + 0, + 'NotHyphenatedLowercase', + array( $expected, $file_name ) + ); + } + + + /** + * Check files containing a class for the "class-" prefix and that the rest of + * the file name reflects the class name. + * + * @since 3.0.0 + * + * @param int $class_ptr Stack pointer to the first T_CLASS in the file. + * @param string $file_name The name of the current file. + * + * @return void + */ + protected function check_filename_has_class_prefix( $class_ptr, $file_name ) { + $extension = strrchr( $file_name, '.' ); + $class_name = ObjectDeclarations::getName( $this->phpcsFile, $class_ptr ); + $expected = 'class-' . strtolower( str_replace( '_', '-', $class_name ) ) . $extension; + + if ( $file_name === $expected ) { + return; + } + + $this->phpcsFile->addError( + 'Class file names should be based on the class name with "class-" prepended. Expected %s, but found %s.', + 0, + 'InvalidClassFileName', + array( + $expected, + $file_name, + ) + ); + } + + /** + * Check non-class files in "wp-includes" with a "@subpackage Template" tag for a "-template" suffix. + * + * @since 3.0.0 + * + * @param int $stackPtr Stack pointer to the first PHP open tag in the file. + * @param string $file_name The name of the current file. + * + * @return void + */ + protected function check_filename_for_template_suffix( $stackPtr, $file_name ) { + $subpackage_tag = $this->phpcsFile->findNext( \T_DOC_COMMENT_TAG, $stackPtr, null, false, '@subpackage' ); + if ( false === $subpackage_tag ) { + return; + } + + $subpackage = $this->phpcsFile->findNext( \T_DOC_COMMENT_STRING, $subpackage_tag ); + if ( false === $subpackage ) { + return; + } + + $fileName_end = substr( $file_name, -13 ); + + if ( ( 'Template' === trim( $this->tokens[ $subpackage ]['content'] ) + && $this->tokens[ $subpackage_tag ]['line'] === $this->tokens[ $subpackage ]['line'] ) + && ( ( ! \defined( '\PHP_CODESNIFFER_IN_TESTS' ) && '-template.php' !== $fileName_end ) + || ( \defined( '\PHP_CODESNIFFER_IN_TESTS' ) && '-template.inc' !== $fileName_end ) ) + ) { + $this->phpcsFile->addError( + 'Files containing template tags should have "-template" appended to the end of the file name. Expected %s, but found %s.', + 0, + 'InvalidTemplateTagFileName', + array( + substr( $file_name, 0, -4 ) . '-template.php', + $file_name, + ) + ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php new file mode 100644 index 00000000..b04a652f --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php @@ -0,0 +1,1287 @@ + Key is prefix, value irrelevant. + */ + protected $prefix_blocklist = array( + 'wordpress' => true, + 'wp' => true, + '_' => true, + 'php' => true, // See #1728, the 'php' prefix is reserved by PHP itself. + ); + + /** + * Target prefixes after validation. + * + * All prefixes are lowercased for case-insensitive compare. + * + * @since 0.12.0 + * + * @var array + */ + private $validated_prefixes = array(); + + /** + * Target namespace prefixes after validation with regex indicator. + * + * All prefixes are lowercased for case-insensitive compare. + * If the prefix doesn't already contain a namespace separator, but does contain + * non-word characters, these will have been replaced with regex syntax to allow + * for namespace separators and the `is_regex` indicator will have been set to `true`. + * + * @since 1.2.0 + * + * @var array> + */ + private $validated_namespace_prefixes = array(); + + /** + * Cache of previously set prefixes. + * + * Prevents having to do the same prefix validation over and over again. + * + * @since 0.12.0 + * + * @var string[] + */ + private $previous_prefixes = array(); + + /** + * A list of core hooks that are allowed to be called by plugins and themes. + * + * @since 0.14.0 + * @since 3.0.0 Renamed from `$whitelisted_core_hooks` to `$allowed_core_hooks`. + * + * @var array Key is hook name, value irrelevant. + */ + protected $allowed_core_hooks = array( + 'widget_title' => true, + 'add_meta_boxes' => true, + ); + + /** + * A list of core constants that are allowed to be defined by plugins and themes. + * + * Source: {@link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/default-constants.php#L0} + * The constants are listed in alphabetic order. + * Only overrulable constants are listed, i.e. those defined within core within + * a `if ( ! defined() ) {}` wrapper. + * + * Last update: July 2023 for WP 6.3 at https://github.com/WordPress/wordpress-develop/commit/6281ce432c50345a57768bf53854d9b65b6cdd52 + * + * @since 1.0.0 + * @since 3.0.0 Renamed from `$whitelisted_core_constants` to `$allowed_core_constants`. + * + * @var array Key is constant name, value irrelevant. + */ + protected $allowed_core_constants = array( + 'ADMIN_COOKIE_PATH' => true, + 'AUTH_COOKIE' => true, + 'AUTOSAVE_INTERVAL' => true, + 'COOKIEHASH' => true, + 'COOKIEPATH' => true, + 'COOKIE_DOMAIN' => true, + 'EMPTY_TRASH_DAYS' => true, + 'FORCE_SSL_ADMIN' => true, + 'FORCE_SSL_LOGIN' => true, // Deprecated. + 'LOGGED_IN_COOKIE' => true, + 'MEDIA_TRASH' => true, + 'MUPLUGINDIR' => true, // Deprecated. + 'PASS_COOKIE' => true, + 'PLUGINDIR' => true, // Deprecated. + 'PLUGINS_COOKIE_PATH' => true, + 'RECOVERY_MODE_COOKIE' => true, + 'SCRIPT_DEBUG' => true, + 'SECURE_AUTH_COOKIE' => true, + 'SHORTINIT' => true, + 'SITECOOKIEPATH' => true, + 'TEST_COOKIE' => true, + 'USER_COOKIE' => true, + 'WPMU_PLUGIN_DIR' => true, + 'WPMU_PLUGIN_URL' => true, + 'WP_CACHE' => true, + 'WP_CONTENT_DIR' => true, + 'WP_CONTENT_URL' => true, + 'WP_CRON_LOCK_TIMEOUT' => true, + 'WP_DEBUG' => true, + 'WP_DEBUG_DISPLAY' => true, + 'WP_DEBUG_LOG' => true, + 'WP_DEFAULT_THEME' => true, + 'WP_DEVELOPMENT_MODE' => true, + 'WP_MAX_MEMORY_LIMIT' => true, + 'WP_MEMORY_LIMIT' => true, + 'WP_PLUGIN_DIR' => true, + 'WP_PLUGIN_URL' => true, + 'WP_POST_REVISIONS' => true, + 'WP_START_TIMESTAMP' => true, + ); + + /** + * A list of functions declared in WP core as "Pluggable", i.e. overloadable from a plugin. + * + * Note: deprecated functions should still be included in this list as plugins may support older WP versions. + * + * @since 3.0.0. + * + * @var array Key is function name, value irrelevant. + */ + protected $pluggable_functions = array( + 'auth_redirect' => true, + 'cache_users' => true, + 'check_admin_referer' => true, + 'check_ajax_referer' => true, + 'get_avatar' => true, + 'get_currentuserinfo' => true, // Deprecated. + 'get_user_by' => true, + 'get_user_by_email' => true, // Deprecated. + 'get_userdata' => true, + 'get_userdatabylogin' => true, // Deprecated. + 'graceful_fail' => true, + 'install_global_terms' => true, + 'install_network' => true, + 'is_user_logged_in' => true, + // 'lowercase_octets' => true, => unclear if this function is meant to be publicly pluggable. + 'maybe_add_column' => true, + 'maybe_create_table' => true, + 'set_current_user' => true, // Deprecated. + 'twenty_twenty_one_entry_meta_footer' => true, + 'twenty_twenty_one_post_thumbnail' => true, + 'twenty_twenty_one_post_title' => true, + 'twenty_twenty_one_posted_by' => true, + 'twenty_twenty_one_posted_on' => true, + 'twenty_twenty_one_setup' => true, + 'twenty_twenty_one_the_posts_navigation' => true, + 'twentyeleven_admin_header_image' => true, + 'twentyeleven_admin_header_style' => true, + 'twentyeleven_comment' => true, + 'twentyeleven_content_nav' => true, + 'twentyeleven_continue_reading_link' => true, + 'twentyeleven_header_style' => true, + 'twentyeleven_posted_on' => true, + 'twentyeleven_setup' => true, + 'twentyfifteen_comment_nav' => true, + 'twentyfifteen_entry_meta' => true, + 'twentyfifteen_excerpt_more' => true, + 'twentyfifteen_fonts_url' => true, + 'twentyfifteen_get_color_scheme' => true, + 'twentyfifteen_get_color_scheme_choices' => true, + 'twentyfifteen_get_link_url' => true, + 'twentyfifteen_header_style' => true, + 'twentyfifteen_post_thumbnail' => true, + 'twentyfifteen_sanitize_color_scheme' => true, + 'twentyfifteen_setup' => true, + 'twentyfifteen_the_custom_logo' => true, + 'twentyfourteen_admin_header_image' => true, + 'twentyfourteen_admin_header_style' => true, + 'twentyfourteen_excerpt_more' => true, + 'twentyfourteen_font_url' => true, + 'twentyfourteen_header_style' => true, + 'twentyfourteen_list_authors' => true, + 'twentyfourteen_paging_nav' => true, + 'twentyfourteen_post_nav' => true, + 'twentyfourteen_post_thumbnail' => true, + 'twentyfourteen_posted_on' => true, + 'twentyfourteen_setup' => true, + 'twentyfourteen_the_attached_image' => true, + 'twentynineteen_comment_count' => true, + 'twentynineteen_comment_form' => true, + 'twentynineteen_discussion_avatars_list' => true, + 'twentynineteen_entry_footer' => true, + 'twentynineteen_get_user_avatar_markup' => true, + 'twentynineteen_post_thumbnail' => true, + 'twentynineteen_posted_by' => true, + 'twentynineteen_posted_on' => true, + 'twentynineteen_setup' => true, + 'twentynineteen_the_posts_navigation' => true, + 'twentyseventeen_edit_link' => true, + 'twentyseventeen_entry_footer' => true, + 'twentyseventeen_fonts_url' => true, + 'twentyseventeen_header_style' => true, + 'twentyseventeen_posted_on' => true, + 'twentyseventeen_time_link' => true, + 'twentysixteen_categorized_blog' => true, + 'twentysixteen_entry_date' => true, + 'twentysixteen_entry_meta' => true, + 'twentysixteen_entry_taxonomies' => true, + 'twentysixteen_excerpt' => true, + 'twentysixteen_excerpt_more' => true, + 'twentysixteen_fonts_url' => true, + 'twentysixteen_get_color_scheme' => true, + 'twentysixteen_get_color_scheme_choices' => true, + 'twentysixteen_header_style' => true, + 'twentysixteen_post_thumbnail' => true, + 'twentysixteen_sanitize_color_scheme' => true, + 'twentysixteen_setup' => true, + 'twentysixteen_the_custom_logo' => true, + 'twentyten_admin_header_style' => true, + 'twentyten_comment' => true, + 'twentyten_continue_reading_link' => true, + 'twentyten_posted_in' => true, + 'twentyten_posted_on' => true, + 'twentyten_setup' => true, + 'twentythirteen_entry_date' => true, + 'twentythirteen_entry_meta' => true, + 'twentythirteen_excerpt_more' => true, + 'twentythirteen_fonts_url' => true, + 'twentythirteen_paging_nav' => true, + 'twentythirteen_post_nav' => true, + 'twentythirteen_the_attached_image' => true, + 'twentytwelve_comment' => true, + 'twentytwelve_content_nav' => true, + 'twentytwelve_entry_meta' => true, + 'twentytwelve_get_font_url' => true, + 'twentytwenty_customize_partial_blogdescription' => true, + 'twentytwenty_customize_partial_blogname' => true, + 'twentytwenty_customize_partial_site_logo' => true, + 'twentytwenty_generate_css' => true, + 'twentytwenty_get_customizer_css' => true, + 'twentytwenty_get_theme_svg' => true, + 'twentytwenty_the_theme_svg' => true, + 'twentytwentytwo_styles' => true, + 'twentytwentytwo_support' => true, + 'wp_authenticate' => true, + 'wp_cache_add_multiple' => true, + 'wp_cache_delete_multiple' => true, + 'wp_cache_flush_group' => true, + 'wp_cache_flush_runtime' => true, + 'wp_cache_get_multiple' => true, + 'wp_cache_set_multiple' => true, + 'wp_cache_supports' => true, + 'wp_check_password' => true, + 'wp_clear_auth_cookie' => true, + 'wp_clearcookie' => true, // Deprecated. + 'wp_create_nonce' => true, + 'wp_generate_auth_cookie' => true, + 'wp_generate_password' => true, + 'wp_get_cookie_login' => true, // Deprecated. + 'wp_get_current_user' => true, + // 'wp_handle_upload_error' => true, => unclear if this function is meant to be publicly pluggable. + 'wp_hash' => true, + 'wp_hash_password' => true, + 'wp_install' => true, + 'wp_install_defaults' => true, + 'wp_login' => true, // Deprecated. + 'wp_logout' => true, + 'wp_mail' => true, + 'wp_new_blog_notification' => true, + 'wp_new_user_notification' => true, + 'wp_nonce_tick' => true, + 'wp_notify_moderator' => true, + 'wp_notify_postauthor' => true, + 'wp_parse_auth_cookie' => true, + 'wp_password_change_notification' => true, + 'wp_rand' => true, + 'wp_redirect' => true, + 'wp_safe_redirect' => true, + 'wp_salt' => true, + 'wp_sanitize_redirect' => true, + 'wp_set_auth_cookie' => true, + 'wp_set_current_user' => true, + 'wp_set_password' => true, + 'wp_setcookie' => true, // Deprecated. + 'wp_text_diff' => true, + 'wp_upgrade' => true, + 'wp_validate_auth_cookie' => true, + 'wp_validate_redirect' => true, + 'wp_verify_nonce' => true, + ); + + /** + * A list of classes declared in WP core as "Pluggable", i.e. overloadable from a plugin. + * + * Source: {@link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/pluggable.php} + * and {@link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/pluggable-deprecated.php} + * + * Note: deprecated classes should still be included in this list as plugins may support older WP versions. + * + * @since 3.0.0. + * + * @var array Key is class name, value irrelevant. + */ + protected $pluggable_classes = array( + 'TwentyTwenty_Customize' => true, + 'TwentyTwenty_Non_Latin_Languages' => true, + 'TwentyTwenty_SVG_Icons' => true, + 'TwentyTwenty_Script_Loader' => true, + 'TwentyTwenty_Separator_Control' => true, + 'TwentyTwenty_Walker_Comment' => true, + 'TwentyTwenty_Walker_Page' => true, + 'Twenty_Twenty_One_Customize' => true, + 'WP_User_Search' => true, + 'wp_atom_server' => true, // Deprecated. + ); + + /** + * List of all PHP native functions. + * + * Using this list rather than a call to `function_exists()` prevents + * false negatives from user-defined functions when those would be + * autoloaded via a Composer autoload files directives. + * + * @var array + */ + private $built_in_functions; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.12.0 + * + * @return array + */ + public function register() { + // Get a list of all PHP native functions. + $all_functions = get_defined_functions(); + $this->built_in_functions = array_flip( $all_functions['internal'] ); + $this->built_in_functions = array_change_key_case( $this->built_in_functions, \CASE_LOWER ); + + // Make sure the pluggable functions and classes list can be easily compared. + $this->pluggable_functions = array_change_key_case( $this->pluggable_functions, \CASE_LOWER ); + $this->pluggable_classes = array_change_key_case( $this->pluggable_classes, \CASE_LOWER ); + + // Set the sniff targets. + $targets = array( + \T_NAMESPACE => \T_NAMESPACE, + \T_FUNCTION => \T_FUNCTION, + \T_CONST => \T_CONST, + \T_VARIABLE => \T_VARIABLE, + \T_DOLLAR => \T_DOLLAR, // Variable variables. + \T_FN_ARROW => \T_FN_ARROW, // T_FN_ARROW is only used for skipping over (for now). + ); + $targets += Tokens::$ooScopeTokens; // T_ANON_CLASS is only used for skipping over test classes. + $targets += Collections::listOpenTokensBC(); + + // Add function call target for hook names and constants defined using define(). + $parent = parent::register(); + if ( ! empty( $parent ) ) { + $targets[] = \T_STRING; + } + + return $targets; + } + + /** + * Groups of functions to restrict. + * + * @since 0.12.0 + * + * @return array + */ + public function getGroups() { + // Only retrieve functions which are not used for deprecated hooks. + $this->target_functions = WPHookHelper::get_functions( false ); + $this->target_functions['define'] = true; + + return parent::getGroups(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + // Allow overruling the prefixes set in a ruleset via the command line. + $cl_prefixes = Helper::getConfigData( 'prefixes' ); + if ( ! empty( $cl_prefixes ) ) { + $cl_prefixes = trim( $cl_prefixes ); + if ( '' !== $cl_prefixes ) { + $this->prefixes = array_filter( array_map( 'trim', explode( ',', $cl_prefixes ) ) ); + } + } + + $this->prefixes = RulesetPropertyHelper::merge_custom_array( $this->prefixes, array(), false ); + if ( empty( $this->prefixes ) ) { + // No prefixes passed, nothing to do. + return; + } + + $this->validate_prefixes(); + if ( empty( $this->validated_prefixes ) ) { + // No _valid_ prefixes passed, nothing to do. + return; + } + + // Ignore test classes. + if ( isset( Tokens::$ooScopeTokens[ $this->tokens[ $stackPtr ]['code'] ] ) + && true === $this->is_test_class( $this->phpcsFile, $stackPtr ) + ) { + if ( $this->tokens[ $stackPtr ]['scope_condition'] === $stackPtr && isset( $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + // Skip forward to end of test class. + return $this->tokens[ $stackPtr ]['scope_closer']; + } + return; + } + + if ( \T_ANON_CLASS === $this->tokens[ $stackPtr ]['code'] ) { + // Token was only registered to allow skipping over test classes. + return; + } + + /* + * Ignore the contents of arrow functions which do not declare closures. + * + * - Parameters declared by arrow functions do not need to be prefixed (handled elsewhere). + * - New variables declared within an arrow function are local to the arrow function, so can be ignored. + * - A `global` statement is not allowed within an arrow function. + * + * Note: this does mean some convoluted code may get ignored (false negatives), but this is currently + * not reliably solvable as PHPCS does not add arrow functions to the 'conditions' array. + */ + if ( \T_FN_ARROW === $this->tokens[ $stackPtr ]['code'] + && isset( $this->tokens[ $stackPtr ]['scope_closer'] ) + ) { + $has_closure = $this->phpcsFile->findNext( \T_CLOSURE, ( $stackPtr + 1 ), $this->tokens[ $stackPtr ]['scope_closer'] ); + if ( false !== $has_closure ) { + // Skip to the start of the closure. + return $has_closure; + } + + // Skip the arrow function completely. + return $this->tokens[ $stackPtr ]['scope_closer']; + } + + if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] ) { + // Disallow excluding function groups for this sniff. + $this->exclude = array(); + + return parent::process_token( $stackPtr ); + + } elseif ( \T_DOLLAR === $this->tokens[ $stackPtr ]['code'] ) { + + return $this->process_variable_variable( $stackPtr ); + + } elseif ( \T_VARIABLE === $this->tokens[ $stackPtr ]['code'] ) { + + return $this->process_variable_assignment( $stackPtr ); + + } elseif ( isset( Collections::listOpenTokensBC()[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + return $this->process_list_assignment( $stackPtr ); + + } elseif ( \T_NAMESPACE === $this->tokens[ $stackPtr ]['code'] ) { + $namespace_name = Namespaces::getDeclaredName( $this->phpcsFile, $stackPtr ); + + if ( false === $namespace_name || '' === $namespace_name || '\\' === $namespace_name ) { + return; + } + + foreach ( $this->validated_namespace_prefixes as $key => $prefix_info ) { + if ( false === $prefix_info['is_regex'] ) { + if ( stripos( $namespace_name, $prefix_info['prefix'] ) === 0 ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $key ); + return; + } + } else { + // Ok, so this prefix should be used as a regex. + $regex = '`^' . $prefix_info['prefix'] . '`i'; + if ( preg_match( $regex, $namespace_name ) > 0 ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $key ); + return; + } + } + } + + // Still here ? In that case, we have a non-prefixed namespace name. + $recorded = $this->phpcsFile->addError( + self::ERROR_MSG, + $stackPtr, + 'NonPrefixedNamespaceFound', + array( + 'Namespaces declared', + $namespace_name, + ) + ); + + if ( true === $recorded ) { + $this->record_potential_prefix_metric( $stackPtr, $namespace_name ); + } + + return; + + } else { + + // Namespaced methods, classes and constants do not need to be prefixed. + $namespace = Namespaces::determineNamespace( $this->phpcsFile, $stackPtr ); + if ( '' !== $namespace && '\\' !== $namespace ) { + return; + } + + $item_name = ''; + $error_text = 'Unknown syntax used'; + $error_code = 'NonPrefixedSyntaxFound'; + + switch ( $this->tokens[ $stackPtr ]['code'] ) { + case \T_FUNCTION: + // Methods in a class do not need to be prefixed. + if ( Scopes::isOOMethod( $this->phpcsFile, $stackPtr ) === true ) { + return; + } + + if ( DeprecationHelper::is_function_deprecated( $this->phpcsFile, $stackPtr ) === true ) { + /* + * Deprecated functions don't have to comply with the naming conventions, + * otherwise functions deprecated in favour of a function with a compliant + * name would still trigger an error. + */ + return; + } + + $item_name = FunctionDeclarations::getName( $this->phpcsFile, $stackPtr ); + $item_lc = strtolower( $item_name ); + if ( isset( $this->built_in_functions[ $item_lc ] ) ) { + // Backfill for PHP native function. + return; + } + + if ( isset( $this->pluggable_functions[ $item_lc ] ) ) { + // Pluggable function should not be prefixed. + return; + } + + $error_text = 'Functions declared in the global namespace'; + $error_code = 'NonPrefixedFunctionFound'; + break; + + case \T_CLASS: + case \T_INTERFACE: + case \T_TRAIT: + case \T_ENUM: + $item_name = ObjectDeclarations::getName( $this->phpcsFile, $stackPtr ); + $error_text = 'Classes declared'; + $error_code = 'NonPrefixedClassFound'; + + switch ( $this->tokens[ $stackPtr ]['code'] ) { + case \T_CLASS: + if ( isset( $this->pluggable_classes[ strtolower( $item_name ) ] ) ) { + // Pluggable class should not be prefixed. + return; + } + + if ( class_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native class. + return; + } + break; + + case \T_INTERFACE: + if ( interface_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native interface. + return; + } + + $error_text = 'Interfaces declared'; + $error_code = 'NonPrefixedInterfaceFound'; + break; + + case \T_TRAIT: + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.trait_existsFound + if ( function_exists( '\trait_exists' ) && trait_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native trait. + return; + } + + $error_text = 'Traits declared'; + $error_code = 'NonPrefixedTraitFound'; + break; + + case \T_ENUM: + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.enum_existsFound + if ( function_exists( '\enum_exists' ) && enum_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native enum. + return; + } + + $error_text = 'Enums declared'; + $error_code = 'NonPrefixedEnumFound'; + break; + } + + break; + + case \T_CONST: + // Constants in an OO construct do not need to be prefixed. + if ( true === Scopes::isOOConstant( $this->phpcsFile, $stackPtr ) ) { + return; + } + + $constant_name_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $constant_name_ptr ) { + // Live coding. + return; + } + + $item_name = $this->tokens[ $constant_name_ptr ]['content']; + if ( \defined( '\\' . $item_name ) ) { + // Backfill for PHP native constant. + return; + } + + if ( isset( $this->allowed_core_constants[ $item_name ] ) ) { + // Defining a WP Core constant intended for overruling. + return; + } + + $error_text = 'Global constants defined'; + $error_code = 'NonPrefixedConstantFound'; + break; + + default: + // Left empty on purpose. + break; + + } + + if ( empty( $item_name ) || $this->is_prefixed( $stackPtr, $item_name ) === true ) { + return; + } + + $recorded = $this->phpcsFile->addError( + self::ERROR_MSG, + $stackPtr, + $error_code, + array( + $error_text, + $item_name, + ) + ); + + if ( true === $recorded ) { + $this->record_potential_prefix_metric( $stackPtr, $item_name ); + } + } + } + + /** + * Handle variable variables defined in the global namespace. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_variable_variable( $stackPtr ) { + static $indicators = array( + \T_OPEN_CURLY_BRACKET => true, + \T_VARIABLE => true, + ); + + // Is this a variable variable ? + // Not concerned with nested ones as those will be recognized on their own token. + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $next_non_empty || ! isset( $indicators[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { + return; + } + + if ( \T_OPEN_CURLY_BRACKET === $this->tokens[ $next_non_empty ]['code'] + && isset( $this->tokens[ $next_non_empty ]['bracket_closer'] ) + ) { + // Skip over the variable part. + $next_non_empty = $this->tokens[ $next_non_empty ]['bracket_closer']; + } + + $maybe_assignment = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true, null, true ); + + while ( false !== $maybe_assignment + && \T_OPEN_SQUARE_BRACKET === $this->tokens[ $maybe_assignment ]['code'] + && isset( $this->tokens[ $maybe_assignment ]['bracket_closer'] ) + ) { + $maybe_assignment = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + ( $this->tokens[ $maybe_assignment ]['bracket_closer'] + 1 ), + null, + true, + null, + true + ); + } + + if ( false === $maybe_assignment ) { + return; + } + + if ( ! isset( Tokens::$assignmentTokens[ $this->tokens[ $maybe_assignment ]['code'] ] ) ) { + // Not an assignment. + return; + } + + $error = self::ERROR_MSG; + + /* + * Local variable variables in a function do not need to be prefixed. + * But a variable variable could evaluate to the name of an imported global + * variable. + * Not concerned with imported variable variables (global.. ) as that has been + * forbidden since PHP 7.0. Presuming cross-version code and if not, that + * is for the PHPCompatibility standard to detect. + */ + $functionPtr = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( false !== $functionPtr ) { + $has_global = $this->phpcsFile->findPrevious( \T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $functionPtr ]['scope_opener'] ); + if ( false === $has_global ) { + // No variable import happening. + return; + } + + $error = 'Variable variable which could potentially override an imported global variable detected. ' . $error; + } + + $variable_name = $this->phpcsFile->getTokensAsString( $stackPtr, ( ( $next_non_empty - $stackPtr ) + 1 ) ); + + // Still here ? In that case, the variable name should be prefixed. + $recorded = $this->phpcsFile->addWarning( + $error, + $stackPtr, + 'NonPrefixedVariableFound', + array( + 'Global variables defined', + $variable_name, + ) + ); + + if ( true === $recorded ) { + $this->record_potential_prefix_metric( $stackPtr, $variable_name ); + } + + // Skip over the variable part of the variable. + return ( $next_non_empty + 1 ); + } + + /** + * Check that defined global variables are prefixed. + * + * @since 0.12.0 + * @since 2.2.0 Added $in_list parameter. + * + * @param int $stackPtr The position of the current token in the stack. + * @param bool $in_list Whether or not this is a variable in a list assignment. + * Defaults to false. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_variable_assignment( $stackPtr, $in_list = false ) { + /* + * We're only concerned with variables which are being defined. + * `is_assigment()` will not recognize property assignments, which is good in this case. + * However it will also not recognize $b in `foreach( $a as $b )` as an assignment, so + * we need a separate check for that. + */ + if ( false === $in_list + && false === VariableHelper::is_assignment( $this->phpcsFile, $stackPtr ) + && Context::inForeachCondition( $this->phpcsFile, $stackPtr ) !== 'afterAs' + ) { + return; + } + + $is_error = true; + $variable_name = substr( $this->tokens[ $stackPtr ]['content'], 1 ); // Strip the dollar sign. + + // Bow out early if we know for certain no prefix is needed. + if ( 'GLOBALS' !== $variable_name + && $this->variable_prefixed_or_allowed( $stackPtr, $variable_name ) === true + ) { + return; + } + + if ( 'GLOBALS' === $variable_name ) { + $array_open = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $array_open || \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $array_open ]['code'] ) { + // Live coding or something very silly. + return; + } + + $array_key = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $array_open + 1 ), null, true, null, true ); + if ( false === $array_key ) { + // No key found, nothing to do. + return; + } + + $stackPtr = $array_key; + $variable_name = TextStrings::stripQuotes( $this->tokens[ $array_key ]['content'] ); + + // Check whether a prefix is needed. + if ( isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] ) + && $this->variable_prefixed_or_allowed( $stackPtr, $variable_name ) === true + ) { + return; + } + + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $array_key ]['code'] ) { + // If the array key is a double quoted string, try again with only + // the part before the first variable (if any). + $exploded = explode( '$', $variable_name ); + $first = rtrim( $exploded[0], '{' ); + if ( '' !== $first ) { + if ( $this->variable_prefixed_or_allowed( $array_key, $first ) === true ) { + return; + } + } else { + // If the first part was dynamic, throw a warning. + $is_error = false; + } + } elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] ) ) { + // Dynamic array key, throw a warning. + $is_error = false; + } + } else { + // Function parameters do not need to be prefixed. + if ( false === $in_list ) { + $functionPtr = Parentheses::getLastOwner( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( false !== $functionPtr ) { + return; + } + unset( $functionPtr ); + } + + // Properties in a class do not need to be prefixed. + if ( false === $in_list && true === Scopes::isOOProperty( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // Local variables in a function do not need to be prefixed unless they are being imported. + $functionPtr = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( false !== $functionPtr ) { + $has_global = $this->phpcsFile->findPrevious( \T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $functionPtr ]['scope_opener'] ); + if ( false === $has_global + || Conditions::getLastCondition( $this->phpcsFile, $has_global, Collections::functionDeclarationTokens() ) !== $functionPtr + ) { + // No variable import happening in the current scope. + return; + } + + // Ok, this may be an imported global variable. + $end_of_statement = $this->phpcsFile->findNext( array( \T_SEMICOLON, \T_CLOSE_TAG ), ( $has_global + 1 ) ); + if ( false === $end_of_statement ) { + // No semi-colon - live coding. + return; + } + + for ( $ptr = ( $has_global + 1 ); $ptr <= $end_of_statement; $ptr++ ) { + // Move the stack pointer to the next variable. + $ptr = $this->phpcsFile->findNext( \T_VARIABLE, $ptr, $end_of_statement, false, null, true ); + + if ( false === $ptr ) { + // Reached the end of the global statement without finding the variable, + // so this must be a local variable. + return; + } + + if ( substr( $this->tokens[ $ptr ]['content'], 1 ) === $variable_name ) { + break; + } + } + + unset( $has_global, $end_of_statement, $ptr ); + } + } + + // Still here ? In that case, the variable name should be prefixed. + $recorded = MessageHelper::addMessage( + $this->phpcsFile, + self::ERROR_MSG, + $stackPtr, + $is_error, + 'NonPrefixedVariableFound', + array( + 'Global variables defined', + '$' . $variable_name, + ) + ); + + if ( true === $recorded ) { + $this->record_potential_prefix_metric( $stackPtr, $variable_name ); + } + } + + /** + * Check that global variables declared via a list construct are prefixed. + * + * {@internal No need to take special measures for nested lists. Nested or not, + * each list part can only contain one variable being written to.} + * + * @since 2.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_list_assignment( $stackPtr ) { + $list_open_close = Lists::getOpenClose( $this->phpcsFile, $stackPtr ); + if ( false === $list_open_close ) { + // Short array, not short list. + return; + } + + $var_pointers = ListHelper::get_list_variables( $this->phpcsFile, $stackPtr ); + foreach ( $var_pointers as $ptr ) { + $this->process_variable_assignment( $ptr, true ); + } + + // No need to re-examine these variables. + return $list_open_close['closer']; + } + + /** + * Process the parameters of a matched function. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + if ( 'define' === $matched_content ) { + $target_param = PassedParameters::getParameterFromStack( $parameters, 1, 'constant_name' ); + + } else { + $target_param = WPHookHelper::get_hook_name_param( $matched_content, $parameters ); + } + + if ( false === $target_param ) { + return; + } + + $is_error = true; + $clean_content = TextStrings::stripQuotes( $target_param['clean'] ); + + if ( ( 'define' !== $matched_content + && isset( $this->allowed_core_hooks[ $clean_content ] ) ) + || ( 'define' === $matched_content + && isset( $this->allowed_core_constants[ $clean_content ] ) ) + ) { + return; + } + + if ( $this->is_prefixed( $target_param['start'], $clean_content ) === true ) { + return; + } else { + // This may be a dynamic hook/constant name. + $first_non_empty = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $target_param['start'], + ( $target_param['end'] + 1 ), + true + ); + + if ( false === $first_non_empty ) { + return; + } + + $first_non_empty_content = TextStrings::stripQuotes( $this->tokens[ $first_non_empty ]['content'] ); + + // Try again with just the first token if it's a text string. + if ( isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) + && $this->is_prefixed( $target_param['start'], $first_non_empty_content ) === true + ) { + return; + } + + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $first_non_empty ]['code'] ) { + // If the first part of the parameter is a double quoted string, try again with only + // the part before the first variable (if any). + $exploded = explode( '$', $first_non_empty_content ); + $first = rtrim( $exploded[0], '{' ); + if ( '' !== $first ) { + if ( $this->is_prefixed( $target_param['start'], $first ) === true ) { + return; + } + } else { + // Start of hook/constant name is dynamic, throw a warning. + $is_error = false; + } + } elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) ) { + // Dynamic hook/constant name, throw a warning. + $is_error = false; + } + } + + if ( 'define' === $matched_content ) { + if ( \defined( '\\' . $clean_content ) ) { + // Backfill for PHP native constant. + return; + } + + if ( strpos( $clean_content, '\\' ) !== false ) { + // Namespaced or unreachable constant. + return; + } + + $data = array( 'Global constants defined' ); + $error_code = 'NonPrefixedConstantFound'; + if ( false === $is_error ) { + $error_code = 'VariableConstantNameFound'; + } + } else { + $data = array( 'Hook names invoked' ); + $error_code = 'NonPrefixedHooknameFound'; + if ( false === $is_error ) { + $error_code = 'DynamicHooknameFound'; + } + } + + $data[] = $clean_content; + + $recorded = MessageHelper::addMessage( $this->phpcsFile, self::ERROR_MSG, $first_non_empty, $is_error, $error_code, $data ); + + if ( true === $recorded ) { + $this->record_potential_prefix_metric( $stackPtr, $clean_content ); + } + } + + /** + * Check if a function/class/constant/variable name is prefixed with one of the expected prefixes. + * + * @since 0.12.0 + * @since 0.14.0 Allows for other non-word characters as well as underscores to better support hook names. + * @since 1.0.0 Does not require a word seperator anymore after a prefix. + * This allows for improved code style independent checking, + * i.e. allows for camelCase naming and the likes. + * @since 1.0.1 - Added $stackPtr parameter. + * - The function now also records metrics about the prefixes encountered. + * + * @param int $stackPtr The position of the token to record the metric against. + * @param string $name Name to check for a prefix. + * + * @return bool True when the name is one of the prefixes or starts with an allowed prefix. + * False otherwise. + */ + private function is_prefixed( $stackPtr, $name ) { + foreach ( $this->validated_prefixes as $prefix ) { + if ( stripos( $name, $prefix ) === 0 ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $prefix ); + return true; + } + } + + return false; + } + + /** + * Check if a variable name might need a prefix. + * + * Prefix is not needed for: + * - superglobals, + * - WP native globals, + * - variables which are already prefixed. + * + * @since 0.12.0 + * @since 1.0.1 Added $stackPtr parameter. + * @since 3.0.0 Renamed from `variable_prefixed_or_whitelisted()` to `variable_prefixed_or_allowed()`. + * + * @param int $stackPtr The position of the token to record the metric against. + * @param string $name Variable name without the dollar sign. + * + * @return bool True if the variable name is allowed or already prefixed. + * False otherwise. + */ + private function variable_prefixed_or_allowed( $stackPtr, $name ) { + // Ignore superglobals and WP global variables. + if ( Variables::isSuperglobalName( $name ) || WPGlobalVariablesHelper::is_wp_global( $name ) ) { + return true; + } + + return $this->is_prefixed( $stackPtr, $name ); + } + + /** + * Validate an array of prefixes as passed through a custom property or via the command line. + * + * Checks that the prefix: + * - is not one of the blocked ones. + * - complies with the PHP rules for valid function, class, variable, constant names. + * + * @since 0.12.0 + * + * @return void + */ + private function validate_prefixes() { + if ( $this->previous_prefixes === $this->prefixes ) { + return; + } + + // Set the cache *before* validation so as to not break the above compare. + $this->previous_prefixes = $this->prefixes; + + // Validate the passed prefix(es). + $prefixes = array(); + $ns_prefixes = array(); + foreach ( $this->prefixes as $key => $prefix ) { + $prefixLC = strtolower( $prefix ); + + if ( isset( $this->prefix_blocklist[ $prefixLC ] ) ) { + $this->phpcsFile->addError( + 'The "%s" prefix is not allowed.', + 0, + 'ForbiddenPrefixPassed', + array( $prefix ) + ); + continue; + } + + $prefix_length = strlen( $prefix ); + if ( function_exists( 'iconv_strlen' ) ) { + $prefix_length = iconv_strlen( $prefix, Helper::getEncoding( $this->phpcsFile ) ); + } + + if ( $prefix_length < self::MIN_PREFIX_LENGTH ) { + $this->phpcsFile->addError( + 'The "%s" prefix is too short. Short prefixes are not unique enough and may cause name collisions with other code.', + 0, + 'ShortPrefixPassed', + array( $prefix ) + ); + continue; + } + + /* + * Validate the prefix against characters allowed for function, class, constant names etc. + * Note: this does not use the PHPCSUtils `NamingConventions::isValidIdentifierName()` method + * as we want to allow namespace separators in the prefixes. + */ + if ( preg_match( '`^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\\\\]*$`', $prefix ) !== 1 ) { + + $this->phpcsFile->addWarning( + 'The "%s" prefix is not a valid namespace/function/class/variable/constant prefix in PHP.', + 0, + 'InvalidPrefixPassed', + array( $prefix ) + ); + } + + // Lowercase the prefix to allow for direct compare. + $prefixes[ $key ] = $prefixLC; + + /* + * Replace non-word characters in the prefix with a regex snippet, but only if the + * string doesn't already contain namespace separators. + */ + $is_regex = false; + if ( strpos( $prefix, '\\' ) === false && preg_match( '`[_\W]`', $prefix ) > 0 ) { + $prefix = preg_replace( '`([_\W])`', '[\\\\\\\\$1]', $prefixLC ); + $is_regex = true; + } + + $ns_prefixes[ $prefixLC ] = array( + 'prefix' => $prefix, + 'is_regex' => $is_regex, + ); + } + + // Set the validated prefixes caches. + $this->validated_prefixes = $prefixes; + $this->validated_namespace_prefixes = $ns_prefixes; + } + + /** + * Record the "potential prefix" metric. + * + * @since 1.0.1 + * + * @param int $stackPtr The position of the token to record the metric against. + * @param string $construct_name Name of the global construct to try and distill a potential prefix from. + * + * @return void + */ + private function record_potential_prefix_metric( $stackPtr, $construct_name ) { + if ( preg_match( '`^([A-Z]*[a-z0-9]*+)`', ltrim( $construct_name, '\$_' ), $matches ) > 0 + && isset( $matches[1] ) && '' !== $matches[1] + ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: potential prefixes - start of non-prefixed construct', strtolower( $matches[1] ) ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php new file mode 100644 index 00000000..a925de6f --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -0,0 +1,188 @@ +phpcsFile, $stackPtr ) === true ) { + /* + * Deprecated functions don't have to comply with the naming conventions, + * otherwise functions deprecated in favour of a function with a compliant + * name would still trigger an error. + */ + return; + } + + $name = FunctionDeclarations::getName( $this->phpcsFile, $stackPtr ); + if ( empty( $name ) === true ) { + // Live coding or parse error. + return; + } + + if ( '' === ltrim( $name, '_' ) ) { + // Ignore special functions, like __(). + return; + } + + $ooPtr = Scopes::validDirectScope( $this->phpcsFile, $stackPtr, Tokens::$ooScopeTokens ); + if ( false === $ooPtr ) { + $this->process_function_declaration( $stackPtr, $name ); + } else { + $this->process_method_declaration( $stackPtr, $name, $ooPtr ); + } + } + + /** + * Processes a function declaration for a function in the global namespace. + * + * @since 0.1.0 + * @since 3.0.0 Renamed from `processTokenOutsideScope()` to `process_function_declaration()`. + * Method signature has been changed as well as this method no longer overloads + * a method from the PEAR sniff which was previously the sniff parent. + * + * @param int $stackPtr The position where this token was found. + * @param string $functionName The name of the function. + * + * @return void + */ + protected function process_function_declaration( $stackPtr, $functionName ) { + // PHP magic functions are exempt from our rules. + if ( FunctionDeclarations::isMagicFunctionName( $functionName ) === true ) { + return; + } + + // Is the function name prefixed with "__" ? + if ( preg_match( '`^__[^_]`', $functionName ) === 1 ) { + $error = 'Function name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; + $errorData = array( $functionName ); + $this->phpcsFile->addError( $error, $stackPtr, 'FunctionDoubleUnderscore', $errorData ); + } + + $suggested_name = SnakeCaseHelper::get_suggestion( $functionName ); + if ( $suggested_name !== $functionName ) { + $error = 'Function name "%s" is not in snake case format, try "%s"'; + $errorData = array( + $functionName, + $suggested_name, + ); + $this->phpcsFile->addError( $error, $stackPtr, 'FunctionNameInvalid', $errorData ); + } + } + + /** + * Processes a method declaration. + * + * @since 0.1.0 + * @since 3.0.0 Renamed from `processTokenWithinScope()` to `process_method_declaration()`. + * Method signature has been changed as well, as this method no longer overloads + * a method from the PEAR sniff which was previously the sniff parent. + * + * @param int $stackPtr The position where this token was found. + * @param string $methodName The name of the method. + * @param int $currScope The position of the current scope. + * + * @return void + */ + protected function process_method_declaration( $stackPtr, $methodName, $currScope ) { + + if ( \T_ANON_CLASS === $this->tokens[ $currScope ]['code'] ) { + $className = '[Anonymous Class]'; + } else { + $className = ObjectDeclarations::getName( $this->phpcsFile, $currScope ); + + // PHP4 constructors are allowed to break our rules. + if ( NamingConventions::isEqual( $methodName, $className ) === true ) { + return; + } + + // PHP4 destructors are allowed to break our rules. + if ( NamingConventions::isEqual( $methodName, '_' . $className ) === true ) { + return; + } + } + + // PHP magic methods are exempt from our rules. + if ( FunctionDeclarations::isMagicMethodName( $methodName ) === true ) { + return; + } + + $extended = ObjectDeclarations::findExtendedClassName( $this->phpcsFile, $currScope ); + $interfaces = ObjectDeclarations::findImplementedInterfaceNames( $this->phpcsFile, $currScope ); + + // If this is a child class or interface implementation, it may have to use camelCase or double underscores. + if ( ! empty( $extended ) || ! empty( $interfaces ) ) { + return; + } + + // Is the method name prefixed with "__" ? + if ( preg_match( '`^__[^_]`', $methodName ) === 1 ) { + $error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; + $errorData = array( $className . '::' . $methodName ); + $this->phpcsFile->addError( $error, $stackPtr, 'MethodDoubleUnderscore', $errorData ); + } + + // Check for all lowercase. + $suggested_name = SnakeCaseHelper::get_suggestion( $methodName ); + if ( $suggested_name !== $methodName ) { + $error = 'Method name "%s" in class %s is not in snake case format, try "%s"'; + $errorData = array( + $methodName, + $className, + $suggested_name, + ); + $this->phpcsFile->addError( $error, $stackPtr, 'MethodNameInvalid', $errorData ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php new file mode 100644 index 00000000..57ce7046 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php @@ -0,0 +1,277 @@ + + * + * + * + * + * + * Provide several extra delimiters as one string: + * + * + * + * + * + * + * @var string + */ + public $additionalWordDelimiters = ''; + + /** + * Regular expression to test for correct punctuation of a hook name. + * + * The placeholder will be replaced by potentially provided additional + * word delimiters in the `prepare_regex()` method. + * + * @var string + */ + protected $punctuation_regex = '`[^\w%s]`'; + + /** + * Groups of functions to restrict. + * + * @since 0.11.0 + * + * @return array + */ + public function getGroups() { + // Only retrieve functions which are not used for deprecated hooks. + $this->target_functions = WPHookHelper::get_functions( false ); + + return parent::getGroups(); + } + + /** + * Process the parameters of a matched function. + * + * @since 0.11.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + + $hook_name_param = WPHookHelper::get_hook_name_param( $matched_content, $parameters ); + if ( false === $hook_name_param ) { + return; + } + + $regex = $this->prepare_regex(); + + $case_errors = 0; + $underscores = 0; + $content = array(); + $expected = array(); + $last_non_empty = null; + + for ( $i = $hook_name_param['start']; $i <= $hook_name_param['end']; $i++ ) { + // Skip past comment tokens. + if ( isset( Tokens::$commentTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + $content[ $i ] = $this->tokens[ $i ]['content']; + $expected[ $i ] = $this->tokens[ $i ]['content']; + + // Skip past potential variable array access: `$var['key']`. + if ( \T_VARIABLE === $this->tokens[ $i ]['code'] ) { + do { + $open_bracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( false === $open_bracket + || \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code'] + || ! isset( $this->tokens[ $open_bracket ]['bracket_closer'] ) + ) { + $last_non_empty = $i; + continue 2; + } + + $i = $this->tokens[ $open_bracket ]['bracket_closer']; + + } while ( isset( $this->tokens[ $i ] ) && $i <= $hook_name_param['end'] ); + + $last_non_empty = $i; + continue; + } + + // Skip over parameters passed to function calls. + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] + && ( \T_STRING === $this->tokens[ $last_non_empty ]['code'] + || \T_VARIABLE === $this->tokens[ $last_non_empty ]['code'] ) + && isset( $this->tokens[ $i ]['parenthesis_closer'] ) + ) { + $i = $this->tokens[ $i ]['parenthesis_closer']; + $last_non_empty = $i; + continue; + } + + // Skip past non text string tokens. + if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) === false ) { + $last_non_empty = $i; + continue; + } + + $last_non_empty = $i; + $string = TextStrings::stripQuotes( $this->tokens[ $i ]['content'] ); + + /* + * Here be dragons - a double quoted string can contain extrapolated variables + * which don't have to comply with these rules. + */ + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] ) { + $transform = $this->transform_complex_string( $string, $regex ); + $case_transform = $this->transform_complex_string( $string, $regex, 'case' ); + $punct_transform = $this->transform_complex_string( $string, $regex, 'punctuation' ); + } else { + $transform = $this->transform( $string, $regex ); + $case_transform = $this->transform( $string, $regex, 'case' ); + $punct_transform = $this->transform( $string, $regex, 'punctuation' ); + } + + if ( $string === $transform ) { + continue; + } + + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] ) { + $expected[ $i ] = '"' . $transform . '"'; + } else { + $expected[ $i ] = '\'' . $transform . '\''; + } + + if ( $string !== $case_transform ) { + ++$case_errors; + } + if ( $string !== $punct_transform ) { + ++$underscores; + } + } + + $first_non_empty = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $hook_name_param['start'], + ( $hook_name_param['end'] + 1 ), + true + ); + + $data = array( + trim( implode( '', $expected ) ), + trim( implode( '', $content ) ), + ); + + if ( $case_errors > 0 ) { + $error = 'Hook names should be lowercase. Expected: %s, but found: %s.'; + $this->phpcsFile->addError( $error, $first_non_empty, 'NotLowercase', $data ); + } + + if ( $underscores > 0 ) { + $error = 'Words in hook names should be separated using underscores. Expected: %s, but found: %s.'; + $this->phpcsFile->addWarning( $error, $first_non_empty, 'UseUnderscores', $data ); + } + } + + /** + * Prepare the punctuation regular expression. + * + * Merges the existing regular expression with potentially provided extra word delimiters to allow. + * This is done 'late' and for each found token as otherwise inline `phpcs:set` directives + * would be ignored. + * + * @return string + */ + protected function prepare_regex() { + $extra = ''; + if ( '' !== $this->additionalWordDelimiters && \is_string( $this->additionalWordDelimiters ) ) { + $extra = preg_quote( $this->additionalWordDelimiters, '`' ); + } + + return sprintf( $this->punctuation_regex, $extra ); + } + + /** + * Transform an arbitrary string to lowercase and replace punctuation and spaces with underscores. + * + * @param string $text_string The target string. + * @param string $regex The punctuation regular expression to use. + * @param string $transform_type Whether to do a partial or complete transform. + * Valid values are: 'full', 'case', 'punctuation'. + * @return string + */ + protected function transform( $text_string, $regex, $transform_type = 'full' ) { + + switch ( $transform_type ) { + case 'case': + return strtolower( $text_string ); + + case 'punctuation': + return preg_replace( $regex, '_', $text_string ); + + case 'full': + default: + return preg_replace( $regex, '_', strtolower( $text_string ) ); + } + } + + /** + * Transform a complex string which may contain variable extrapolation. + * + * @param string $text_string The target string. + * @param string $regex The punctuation regular expression to use. + * @param string $transform_type Whether to do a partial or complete transform. + * Valid values are: 'full', 'case', 'punctuation'. + * @return string + */ + protected function transform_complex_string( $text_string, $regex, $transform_type = 'full' ) { + $plain_text = TextStrings::stripEmbeds( $text_string ); + $embeds = TextStrings::getEmbeds( $text_string ); + + $transformed_text = $this->transform( $plain_text, $regex, $transform_type ); + + // Inject the embeds back into the text string. + foreach ( $embeds as $offset => $embed ) { + $transformed_text = substr_replace( $transformed_text, $embed, $offset, 0 ); + } + + return $transformed_text; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php new file mode 100644 index 00000000..2e01da8a --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php @@ -0,0 +1,228 @@ + Key is function name, value irrelevant. + */ + protected $target_functions = array( + 'register_post_type' => true, + ); + + /** + * Array of reserved post type names which can not be used by themes and plugins. + * + * Source: {@link https://developer.wordpress.org/reference/functions/register_post_type/#reserved-post-types} + * + * Last update: July 2023 for WP 6.3 at https://github.com/WordPress/wordpress-develop/commit/6281ce432c50345a57768bf53854d9b65b6cdd52 + * + * @since 2.2.0 + * + * @var array Key is reserved post type name, value irrelevant. + */ + protected $reserved_names = array( + 'action' => true, // Not a WP post type, but prevents other problems. + 'attachment' => true, + 'author' => true, // Not a WP post type, but prevents other problems. + 'custom_css' => true, + 'customize_changeset' => true, + 'nav_menu_item' => true, + 'oembed_cache' => true, + 'order' => true, // Not a WP post type, but prevents other problems. + 'page' => true, + 'post' => true, + 'revision' => true, + 'theme' => true, // Not a WP post type, but prevents other problems. + 'user_request' => true, + 'wp_block' => true, + 'wp_global_styles' => true, + 'wp_navigation' => true, + 'wp_template' => true, + 'wp_template_part' => true, + ); + + /** + * All valid tokens for in the first parameter of register_post_type(). + * + * Set in `register()`. + * + * @since 2.2.0 + * + * @var array + */ + private $valid_tokens = array(); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 2.2.0 + * + * @return array + */ + public function register() { + $this->valid_tokens = Tokens::$textStringTokens + Tokens::$heredocTokens + Tokens::$emptyTokens; + return parent::register(); + } + + /** + * Process the parameter of a matched function. + * + * Errors on invalid post type names when reserved keywords are used, + * the post type is too long, or contains invalid characters. + * + * @since 2.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $post_type_param = PassedParameters::getParameterFromStack( $parameters, 1, 'post_type' ); + if ( false === $post_type_param || '' === $post_type_param['clean'] ) { + // Error for using empty slug. + $this->phpcsFile->addError( + 'register_post_type() called without a post type slug. The slug must be a non-empty string.', + false === $post_type_param ? $stackPtr : $post_type_param['start'], + 'Empty' + ); + return; + } + + $string_start = $this->phpcsFile->findNext( Collections::textStringStartTokens(), $post_type_param['start'], ( $post_type_param['end'] + 1 ) ); + $string_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $post_type_param['start'], ( $post_type_param['end'] + 1 ) ); + + $has_invalid_tokens = $this->phpcsFile->findNext( $this->valid_tokens, $post_type_param['start'], ( $post_type_param['end'] + 1 ), true ); + if ( false !== $has_invalid_tokens || false === $string_pos ) { + // Check for non string based slug parameter (we cannot determine if this is valid). + $this->phpcsFile->addWarning( + 'The post type slug is not a string literal. It is not possible to automatically determine the validity of this slug. Found: %s.', + $stackPtr, + 'NotStringLiteral', + array( + $post_type_param['clean'], + ), + 3 + ); + return; + } + + $post_type = TextStrings::getCompleteTextString( $this->phpcsFile, $string_start ); + if ( isset( Tokens::$heredocTokens[ $this->tokens[ $string_start ]['code'] ] ) ) { + // Trim off potential indentation from PHP 7.3 flexible heredoc/nowdoc content. + $post_type = ltrim( $post_type ); + } + + $data = array( + $post_type, + ); + + // Warn for dynamic parts in the slug parameter. + if ( 'T_DOUBLE_QUOTED_STRING' === $this->tokens[ $string_pos ]['type'] + || ( 'T_HEREDOC' === $this->tokens[ $string_pos ]['type'] + && strpos( $this->tokens[ $string_pos ]['content'], '$' ) !== false ) + ) { + $this->phpcsFile->addWarning( + 'The post type slug may, or may not, get too long with dynamic contents and could contain invalid characters. Found: "%s".', + $string_pos, + 'PartiallyDynamic', + $data + ); + $post_type = TextStrings::stripEmbeds( $post_type ); + } + + if ( preg_match( self::VALID_POST_TYPE_CHARACTERS, $post_type ) === 0 ) { + // Error for invalid characters. + $this->phpcsFile->addError( + 'register_post_type() called with invalid post type "%s". Post type contains invalid characters. Only lowercase alphanumeric characters, dashes, and underscores are allowed.', + $string_pos, + 'InvalidCharacters', + $data + ); + } + + if ( isset( $this->reserved_names[ $post_type ] ) ) { + // Error for using reserved slug names. + $this->phpcsFile->addError( + 'register_post_type() called with reserved post type "%s". Reserved post types should not be used as they interfere with the functioning of WordPress itself.', + $string_pos, + 'Reserved', + $data + ); + } elseif ( stripos( $post_type, 'wp_' ) === 0 ) { + // Error for using reserved slug prefix. + $this->phpcsFile->addError( + 'The post type passed to register_post_type() uses a prefix reserved for WordPress itself. Found: "%s".', + $string_pos, + 'ReservedPrefix', + $data + ); + } + + // Error for slugs that are too long. + if ( strlen( $post_type ) > self::POST_TYPE_MAX_LENGTH ) { + $this->phpcsFile->addError( + 'A post type slug must not exceed %d characters. Found: "%s" (%d characters).', + $string_pos, + 'TooLong', + array( + self::POST_TYPE_MAX_LENGTH, + $post_type, + strlen( $post_type ), + ) + ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php new file mode 100644 index 00000000..37171540 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -0,0 +1,289 @@ + true, + 'GETID3_ERRORARRAY' => true, + 'is_IE' => true, + 'is_IIS' => true, + 'is_macIE' => true, + 'is_NS4' => true, + 'is_winIE' => true, + 'PHP_SELF' => true, + 'post_ID' => true, + 'tag_ID' => true, + 'user_ID' => true, + ); + + /** + * List of member variables that can have mixed case. + * + * @since 0.9.0 + * @since 0.11.0 Changed from public to protected. + * @since 3.0.0 Renamed from `$whitelisted_mixed_case_member_var_names` to `$allowed_mixed_case_member_var_names`. + * + * @var array + */ + protected $allowed_mixed_case_member_var_names = array( + 'cat_ID' => true, + 'comment_ID' => true, + 'comment_author_IP' => true, + 'comment_post_ID' => true, + 'ID' => true, + 'post_ID' => true, + ); + + /** + * Custom list of properties which can have mixed case. + * + * @since 0.11.0 + * @since 3.0.0 Renamed from `$customPropertiesWhitelist` to `$allowed_custom_properties`. + * + * @var string[] + */ + public $allowed_custom_properties = array(); + + /** + * Cache of previously added custom functions. + * + * Prevents having to do the same merges over and over again. + * + * @since 0.10.0 + * @since 0.11.0 - Name changed from $addedCustomVariables. + * - Changed the format from simple bool to array. + * + * @var array + */ + protected $addedCustomProperties = array( + 'properties' => null, + ); + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + protected function processVariable( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // If it's a php reserved var, then its ok. + if ( Variables::isPHPReservedVarName( $tokens[ $stackPtr ]['content'] ) ) { + return; + } + + // Merge any custom variables with the defaults. + $this->merge_allow_lists(); + + $var_name = ltrim( $tokens[ $stackPtr ]['content'], '$' ); + + // Likewise if it is a mixed-case var used by WordPress core. + if ( isset( $this->wordpress_mixed_case_vars[ $var_name ] ) ) { + return; + } + + $obj_operator = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( \T_OBJECT_OPERATOR === $tokens[ $obj_operator ]['code'] + || \T_NULLSAFE_OBJECT_OPERATOR === $tokens[ $obj_operator ]['code'] + ) { + // Check to see if we are using a variable from an object. + $var = $phpcsFile->findNext( Tokens::$emptyTokens, ( $obj_operator + 1 ), null, true ); + if ( \T_STRING === $tokens[ $var ]['code'] ) { + $bracket = $phpcsFile->findNext( Tokens::$emptyTokens, ( $var + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS !== $tokens[ $bracket ]['code'] ) { + $obj_var_name = $tokens[ $var ]['content']; + + if ( isset( $this->allowed_mixed_case_member_var_names[ $obj_var_name ] ) ) { + return; + } + + $suggested_name = SnakeCaseHelper::get_suggestion( $obj_var_name ); + if ( $suggested_name !== $obj_var_name ) { + $error = 'Object property "$%s" is not in valid snake_case format, try "$%s"'; + $data = array( + $obj_var_name, + $suggested_name, + ); + $phpcsFile->addError( $error, $var, 'UsedPropertyNotSnakeCase', $data ); + } + } + } + } + + $in_class = false; + if ( ContextHelper::has_object_operator_before( $phpcsFile, $stackPtr ) === true ) { + // The variable lives within a class, and is referenced like + // this: MyClass::$_variable or $class->variable. + $in_class = true; + } + + $suggested_name = SnakeCaseHelper::get_suggestion( $var_name ); + if ( $suggested_name !== $var_name ) { + if ( $in_class && ! isset( $this->allowed_mixed_case_member_var_names[ $var_name ] ) ) { + $error = 'Object property "$%s" is not in valid snake_case format, try "$%s"'; + $error_name = 'UsedPropertyNotSnakeCase'; + } elseif ( ! $in_class ) { + $error = 'Variable "$%s" is not in valid snake_case format, try "$%s"'; + $error_name = 'VariableNotSnakeCase'; + } + + if ( isset( $error, $error_name ) ) { + $data = array( + $var_name, + $suggested_name, + ); + $phpcsFile->addError( $error, $stackPtr, $error_name, $data ); + } + } + } + + /** + * Processes class member variables. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + protected function processMemberVar( File $phpcsFile, $stackPtr ) { + // Make sure this is actually an OO property and not an OO method parameter or illegal property declaration. + if ( Scopes::isOOProperty( $phpcsFile, $stackPtr ) === false ) { + return; + } + + // Merge any custom variables with the defaults. + $this->merge_allow_lists(); + + $tokens = $phpcsFile->getTokens(); + $var_name = ltrim( $tokens[ $stackPtr ]['content'], '$' ); + + if ( isset( $this->allowed_mixed_case_member_var_names[ $var_name ] ) ) { + return; + } + + $suggested_name = SnakeCaseHelper::get_suggestion( $var_name ); + if ( $suggested_name !== $var_name ) { + $error = 'Member variable "$%s" is not in valid snake_case format, try "$%s"'; + $data = array( + $var_name, + $suggested_name, + ); + $phpcsFile->addError( $error, $stackPtr, 'PropertyNotSnakeCase', $data ); + } + } + + /** + * Processes the variables found within a double quoted string. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the double quoted + * string. + * + * @return void + */ + protected function processVariableInString( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // There will always be embeds if the processVariableInString() was called. + $embeds = TextStrings::getEmbeds( $tokens[ $stackPtr ]['content'] ); + + // Merge any custom variables with the defaults. + $this->merge_allow_lists(); + + foreach ( $embeds as $embed ) { + // Grab any variables contained in the embed. + if ( preg_match_all( '`\$(\{)?(?[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)(?(1)\})`', $embed, $matches ) === 0 ) { + continue; + } + + foreach ( $matches['name'] as $var_name ) { + // If it's a php reserved var, then its ok. + if ( Variables::isPHPReservedVarName( $var_name ) ) { + continue; + } + + // Likewise if it is a mixed-case var used by WordPress core. + if ( isset( $this->wordpress_mixed_case_vars[ $var_name ] ) ) { + continue; + } + + $suggested_name = SnakeCaseHelper::get_suggestion( $var_name ); + if ( $suggested_name !== $var_name ) { + $error = 'Variable "$%s" is not in valid snake_case format, try "$%s"'; + $data = array( + $var_name, + $suggested_name, + ); + $phpcsFile->addError( $error, $stackPtr, 'InterpolatedVariableNotSnakeCase', $data ); + } + } + } + } + + /** + * Merge a custom allow list provided via a custom ruleset with the predefined allow list, + * if we haven't already. + * + * @since 0.10.0 + * @since 2.0.0 Removed unused $phpcs_file parameter. + * @since 3.0.0 Renamed from `mergeWhiteList()` to `merge_allow_lists()`. + * + * @return void + */ + protected function merge_allow_lists() { + if ( $this->allowed_custom_properties !== $this->addedCustomProperties['properties'] ) { + // Fix property potentially passed as comma-delimited string. + $customProperties = RulesetPropertyHelper::merge_custom_array( $this->allowed_custom_properties, array(), false ); + + $this->allowed_mixed_case_member_var_names = RulesetPropertyHelper::merge_custom_array( + $customProperties, + $this->allowed_mixed_case_member_var_names + ); + + $this->addedCustomProperties['properties'] = $this->allowed_custom_properties; + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php new file mode 100644 index 00000000..af1c2ebf --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php @@ -0,0 +1,63 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'error_log' => array( + 'type' => 'warning', + 'message' => '%s() found. Debug code should not normally be used in production.', + 'functions' => array( + 'error_log', + 'var_dump', + 'var_export', + 'print_r', + 'trigger_error', + 'set_error_handler', + 'debug_backtrace', + 'debug_print_backtrace', + 'wp_debug_backtrace_summary', + ), + ), + + 'prevent_path_disclosure' => array( + 'type' => 'warning', + 'message' => '%s() can lead to full path disclosure.', + 'functions' => array( + 'error_reporting', + 'phpinfo', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php new file mode 100644 index 00000000..058fa0d3 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php @@ -0,0 +1,100 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'serialize' => array( + 'type' => 'warning', + 'message' => '%s() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection', + 'functions' => array( + 'serialize', + 'unserialize', + ), + ), + + 'urlencode' => array( + 'type' => 'warning', + 'message' => '%s() should only be used when dealing with legacy applications rawurlencode() should now be used instead. See https://www.php.net/function.rawurlencode and http://www.faqs.org/rfcs/rfc3986.html', + 'functions' => array( + 'urlencode', + ), + ), + + 'runtime_configuration' => array( + 'type' => 'warning', + 'message' => '%s() found. Changing configuration values at runtime is strongly discouraged.', + 'functions' => array( + 'error_reporting', + 'ini_restore', + 'apache_setenv', + 'putenv', + 'set_include_path', + 'restore_include_path', + // This alias was DEPRECATED in PHP 5.3.0, and REMOVED as of PHP 7.0.0. + 'magic_quotes_runtime', + // Warning This function was DEPRECATED in PHP 5.3.0, and REMOVED as of PHP 7.0.0. + 'set_magic_quotes_runtime', + // Warning This function was removed from most SAPIs in PHP 5.3.0, and was removed from PHP-FPM in PHP 7.0.0. + 'dl', + ), + ), + + 'system_calls' => array( + 'type' => 'warning', + 'message' => '%s() found. PHP system calls are often disabled by server admins.', + 'functions' => array( + 'exec', + 'passthru', + 'proc_open', + 'shell_exec', + 'system', + 'popen', + ), + ), + + 'obfuscation' => array( + 'type' => 'warning', + 'message' => '%s() can be used to obfuscate code which is strongly discouraged. Please verify that the function is used for benign reasons.', + 'functions' => array( + 'base64_decode', + 'base64_encode', + 'convert_uudecode', + 'convert_uuencode', + 'str_rot13', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DontExtractSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DontExtractSniff.php new file mode 100644 index 00000000..0d942527 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/DontExtractSniff.php @@ -0,0 +1,52 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + + 'extract' => array( + 'type' => 'error', + 'message' => '%s() usage is highly discouraged, due to the complexity and unintended issues it might cause.', + 'functions' => array( + 'extract', + ), + ), + + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/IniSetSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/IniSetSniff.php new file mode 100644 index 00000000..b393fbc1 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/IniSetSniff.php @@ -0,0 +1,193 @@ + true, + 'ini_alter' => true, // Alias function name. + ); + + /** + * Array of PHP configuration options that are safe to be manipulated, as changing + * the value of these, won't cause interoperability issues between WP/plugins/themes. + * + * @since 2.1.0 + * @since 3.0.0 Renamed from `$whitelisted_options` to `$safe_options`. + * + * @var array Multidimensional array with parameter details. + * $safe_options = array( + * (string) option name. = array( + * (string[]) 'valid_values' = array() + * ) + * ); + */ + protected $safe_options = array( + 'auto_detect_line_endings' => array(), + 'highlight.bg' => array(), + 'highlight.comment' => array(), + 'highlight.default' => array(), + 'highlight.html' => array(), + 'highlight.keyword' => array(), + 'highlight.string' => array(), + 'short_open_tag' => array( + 'valid_values' => array( 'true', '1', 'on' ), + ), + ); + + /** + * Array of PHP configuration options that are not allowed to be manipulated, as changing + * the value of these, will be problematic for interoperability between WP/plugins/themes. + * + * @since 2.1.0 + * @since 3.0.0 Renamed from `$blacklisted_options` to `$disallowed_options`. + * + * @var array Multidimensional array with parameter details. + * $disallowed_options = array( + * (string) option name. = array( + * (string[]) 'invalid_values' = array() + * (string) 'message' + * ) + * ); + */ + protected $disallowed_options = array( + 'bcmath.scale' => array( + 'message' => 'Use `bcscale()` instead.', + ), + 'display_errors' => array( + 'message' => 'Use `WP_DEBUG_DISPLAY` instead.', + ), + 'error_reporting' => array( + 'message' => 'Use `WP_DEBUG` instead.', + ), + 'filter.default' => array( + 'message' => 'Changing the option value can break other plugins. Use the filter flag constants when calling the Filter functions instead.', + ), + 'filter.default_flags' => array( + 'message' => 'Changing the option value can break other plugins. Use the filter flag constants when calling the Filter functions instead.', + ), + 'iconv.input_encoding' => array( + 'message' => 'This option is not supported since PHP 5.6 - use `iconv_set_encoding()` instead.', + ), + 'iconv.internal_encoding' => array( + 'message' => 'This option is not supported since PHP 5.6 - use `iconv_set_encoding()` instead.', + ), + 'iconv.output_encoding' => array( + 'message' => 'This option is not supported since PHP 5.6 - use `iconv_set_encoding()` instead.', + ), + 'ignore_user_abort' => array( + 'message' => 'Use `ignore_user_abort()` instead.', + ), + 'log_errors' => array( + 'message' => 'Use `WP_DEBUG_LOG` instead.', + ), + 'max_execution_time' => array( + 'message' => 'Use `set_time_limit()` instead.', + ), + 'memory_limit' => array( + 'message' => 'Use `wp_raise_memory_limit()` or hook into the filters in that function.', + ), + 'short_open_tag' => array( + 'invalid_values' => array( 'false', '0', 'off' ), + 'message' => 'Turning off short_open_tag is prohibited as it can break other plugins.', + ), + ); + + /** + * Process the parameter of a matched function. + * + * Errors if an option is found in the disallow-list. Warns as + * 'risky' when the option is not found in the safe-list. + * + * @since 2.1.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $option_param = PassedParameters::getParameterFromStack( $parameters, 1, 'option' ); + $value_param = PassedParameters::getParameterFromStack( $parameters, 2, 'value' ); + + if ( false === $option_param || false === $value_param ) { + // Missing required param. Not the concern of this sniff. Bow out. + return; + } + + $option_name = TextStrings::stripQuotes( $option_param['clean'] ); + $option_value = TextStrings::stripQuotes( $value_param['clean'] ); + if ( isset( $this->safe_options[ $option_name ] ) ) { + $safe_option = $this->safe_options[ $option_name ]; + if ( empty( $safe_option['valid_values'] ) || in_array( strtolower( $option_value ), $safe_option['valid_values'], true ) ) { + return; + } + } + + if ( isset( $this->disallowed_options[ $option_name ] ) ) { + $disallowed_option = $this->disallowed_options[ $option_name ]; + if ( empty( $disallowed_option['invalid_values'] ) + || in_array( strtolower( $option_value ), $disallowed_option['invalid_values'], true ) + ) { + $this->phpcsFile->addError( + 'Found: %s(%s, %s). %s', + $stackPtr, + MessageHelper::stringToErrorcode( $option_name . '_Disallowed' ), + array( + $matched_content, + $option_param['clean'], + $value_param['clean'], + $disallowed_option['message'], + ) + ); + return; + } + } + + $this->phpcsFile->addWarning( + 'Changing configuration values at runtime is strongly discouraged. Found: %s(%s, %s)', + $stackPtr, + 'Risky', + array( + $matched_content, + $option_param['clean'], + $value_param['clean'], + ) + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php new file mode 100644 index 00000000..4817f826 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php @@ -0,0 +1,245 @@ + Key is function name, value irrelevant. + */ + protected $allowedFunctionsList = array( + // Directory extension. + 'chdir' => true, + 'opendir' => true, + 'scandir' => true, + + // File extension. + 'file_exists' => true, + 'file_get_contents' => true, + 'file' => true, + 'fileatime' => true, + 'filectime' => true, + 'filegroup' => true, + 'fileinode' => true, + 'filemtime' => true, + 'fileowner' => true, + 'fileperms' => true, + 'filesize' => true, + 'filetype' => true, + 'fopen' => true, + 'is_dir' => true, + 'is_executable' => true, + 'is_file' => true, + 'is_link' => true, + 'is_readable' => true, + 'is_writable' => true, + 'is_writeable' => true, + 'lstat' => true, + 'mkdir' => true, + 'move_uploaded_file' => true, + 'readfile' => true, + 'readlink' => true, + 'rename' => true, + 'rmdir' => true, + 'stat' => true, + 'unlink' => true, + + // FTP extension. + 'ftp_chdir' => true, + 'ftp_login' => true, + 'ftp_rename' => true, + + // Stream extension. + 'stream_select' => true, + 'stream_set_chunk_size' => true, + + // Zlib extension. + 'deflate_add' => true, + 'deflate_init' => true, + 'inflate_add' => true, + 'inflate_init' => true, + 'readgzfile' => true, + + // LibXML extension. + 'libxml_disable_entity_loader' => true, // PHP 8.0 deprecation warning, but function call still needed in select cases. + + // Miscellaneous other functions. + 'imagecreatefromstring' => true, + 'imagecreatefromwebp' => true, + 'parse_url' => true, // Pre-PHP 5.3.3 an E_WARNING was thrown when URL parsing failed. + 'unserialize' => true, + ); + + /** + * Tokens which are regarded as empty for the purpose of determining + * the name of the called function. + * + * This property is set from within the register() method. + * + * @since 1.1.0 + * + * @var array + */ + private $empty_tokens = array(); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.1.0 + * + * @return array + */ + public function register() { + $this->empty_tokens = Tokens::$emptyTokens; + $this->empty_tokens[ \T_NS_SEPARATOR ] = \T_NS_SEPARATOR; + $this->empty_tokens[ \T_BITWISE_AND ] = \T_BITWISE_AND; + + return array( + \T_ASPERAND, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.1.0 + * + * @param int $stackPtr The position of the current token in the stack. + */ + public function process_token( $stackPtr ) { + // Handle the user-defined custom function list. + $this->customAllowedFunctionsList = RulesetPropertyHelper::merge_custom_array( $this->customAllowedFunctionsList, array(), false ); + $this->customAllowedFunctionsList = array_map( 'strtolower', $this->customAllowedFunctionsList ); + + /* + * Check if the error silencing is done for one of the allowed functions. + * + * @internal The function call name determination is done even when there is no allow list active + * to allow the metrics to be more informative. + */ + $next_non_empty = $this->phpcsFile->findNext( $this->empty_tokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false !== $next_non_empty && \T_STRING === $this->tokens[ $next_non_empty ]['code'] ) { + $has_parenthesis = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true, null, true ); + if ( false !== $has_parenthesis && \T_OPEN_PARENTHESIS === $this->tokens[ $has_parenthesis ]['code'] ) { + $function_name = strtolower( $this->tokens[ $next_non_empty ]['content'] ); + if ( ( true === $this->usePHPFunctionsList + && isset( $this->allowedFunctionsList[ $function_name ] ) === true ) + || ( ! empty( $this->customAllowedFunctionsList ) + && in_array( $function_name, $this->customAllowedFunctionsList, true ) === true ) + ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Error silencing', 'silencing allowed function call: ' . $function_name ); + return; + } + } + } + + $this->context_length = (int) $this->context_length; + $context_length = $this->context_length; + if ( $this->context_length <= 0 ) { + $context_length = 2; + } + + // Prepare the "Found" string to display. + $end_of_statement = BCFile::findEndOfStatement( $this->phpcsFile, $stackPtr, \T_COMMA ); + if ( ( $end_of_statement - $stackPtr ) < $context_length ) { + $context_length = ( $end_of_statement - $stackPtr ); + } + + $found = GetTokensAsString::compact( $this->phpcsFile, $stackPtr, ( $stackPtr + $context_length - 1 ), true ) . '...'; + $error_msg = 'Silencing errors is strongly discouraged. Use proper error checking instead.'; + $data = array(); + if ( $this->context_length > 0 ) { + $error_msg .= ' Found: %s'; + $data[] = $found; + } + + $this->phpcsFile->addWarning( + $error_msg, + $stackPtr, + 'Discouraged', + $data + ); + + if ( isset( $function_name ) ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Error silencing', '@' . $function_name ); + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Error silencing', $found ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php new file mode 100644 index 00000000..72f77ad5 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php @@ -0,0 +1,73 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'ereg' => array( + 'type' => 'error', + 'message' => '%s() has been deprecated since PHP 5.3 and removed in PHP 7.0, please use preg_match() instead.', + 'functions' => array( + 'ereg', + 'eregi', + 'sql_regcase', + ), + ), + + 'ereg_replace' => array( + 'type' => 'error', + 'message' => '%s() has been deprecated since PHP 5.3 and removed in PHP 7.0, please use preg_replace() instead.', + 'functions' => array( + 'ereg_replace', + 'eregi_replace', + ), + ), + + 'split' => array( + 'type' => 'error', + 'message' => '%s() has been deprecated since PHP 5.3 and removed in PHP 7.0, please use explode(), str_split() or preg_split() instead.', + 'functions' => array( + 'split', + 'spliti', + ), + ), + + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/PregQuoteDelimiterSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/PregQuoteDelimiterSniff.php new file mode 100644 index 00000000..3a1ab1ea --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/PregQuoteDelimiterSniff.php @@ -0,0 +1,70 @@ + Key is function name, value irrelevant. + */ + protected $target_functions = array( + 'preg_quote' => true, + ); + + /** + * Process the parameters of a matched function. + * + * @since 1.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + + $delimiter = PassedParameters::getParameterFromStack( $parameters, 2, 'delimiter' ); + if ( false !== $delimiter ) { + return; + } + + $this->phpcsFile->addWarning( + 'Passing the $delimiter parameter to preg_quote() is strongly recommended.', + $stackPtr, + 'Missing' + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php new file mode 100644 index 00000000..f3102e33 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php @@ -0,0 +1,45 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'create_function' => array( + 'type' => 'error', + 'message' => '%s() is deprecated as of PHP 7.2 and removed in PHP 8.0. Please use declared named or anonymous functions instead.', + 'functions' => array( + 'create_function', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/StrictInArraySniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/StrictInArraySniff.php new file mode 100644 index 00000000..dc7aa7aa --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/StrictInArraySniff.php @@ -0,0 +1,122 @@ + Key is the function name. + */ + protected $target_functions = array( + 'in_array' => array( + 'param_position' => 3, + 'param_name' => 'strict', + 'always_needed' => true, + ), + 'array_search' => array( + 'param_position' => 3, + 'param_name' => 'strict', + 'always_needed' => true, + ), + 'array_keys' => array( + 'param_position' => 3, + 'param_name' => 'strict', + 'always_needed' => false, + ), + ); + + /** + * Process the parameters of a matched function. + * + * @since 0.11.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $param_info = $this->target_functions[ $matched_content ]; + + /* + * Check if the strict check is actually needed. + * + * Important! This check only applies to array_keys() in the current form of the sniff + * and has been written to be specific to that function. + * If more functions would be added with 'always_needed' set to `false`, + * this code will need to be adjusted to handle those. + */ + if ( false === $param_info['always_needed'] ) { + $has_filter_value = PassedParameters::getParameterFromStack( $parameters, 2, 'filter_value' ); + if ( false === $has_filter_value ) { + return; + } + } + + $found_parameter = PassedParameters::getParameterFromStack( $parameters, $param_info['param_position'], $param_info['param_name'] ); + if ( false === $found_parameter || 'true' !== strtolower( $found_parameter['clean'] ) ) { + $errorcode = 'MissingTrueStrict'; + + /* + * Use a different error code when `false` is found to allow for excluding + * the warning as this will be a conscious choice made by the dev. + */ + if ( is_array( $found_parameter ) && 'false' === strtolower( $found_parameter['clean'] ) ) { + $errorcode = 'FoundNonStrictFalse'; + } + + $this->phpcsFile->addWarning( + 'Not using strict comparison for %s; supply true for $%s argument.', + ( isset( $found_parameter['start'] ) ? $found_parameter['start'] : $stackPtr ), + $errorcode, + array( $matched_content, $param_info['param_name'] ) + ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/TypeCastsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/TypeCastsSniff.php new file mode 100644 index 00000000..3e07c7fb --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/TypeCastsSniff.php @@ -0,0 +1,90 @@ +tokens[ $stackPtr ]['code']; + $typecast = str_replace( ' ', '', $this->tokens[ $stackPtr ]['content'] ); + $typecast_lc = strtolower( $typecast ); + + switch ( $token_code ) { + case \T_DOUBLE_CAST: + if ( '(float)' !== $typecast_lc ) { + $fix = $this->phpcsFile->addFixableError( + 'Normalized type keywords must be used; expected "(float)" but found "%s"', + $stackPtr, + 'DoubleRealFound', + array( $typecast ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $stackPtr, '(float)' ); + } + } + break; + + case \T_UNSET_CAST: + $this->phpcsFile->addError( + 'Using the "(unset)" cast is forbidden as the type cast is removed in PHP 8.0. Use the "unset()" language construct instead.', + $stackPtr, + 'UnsetFound' + ); + break; + + case \T_BINARY_CAST: + $this->phpcsFile->addWarning( + 'Using binary casting is strongly discouraged. Found: "%s"', + $stackPtr, + 'BinaryFound', + array( $typecast ) + ); + break; + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/YodaConditionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/YodaConditionsSniff.php new file mode 100644 index 00000000..150993c6 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/PHP/YodaConditionsSniff.php @@ -0,0 +1,124 @@ +condition_start_tokens = $starters; + + return array( + \T_IS_EQUAL, + \T_IS_NOT_EQUAL, + \T_IS_IDENTICAL, + \T_IS_NOT_IDENTICAL, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + $start = $this->phpcsFile->findPrevious( $this->condition_start_tokens, $stackPtr, null, false, null, true ); + + $needs_yoda = false; + + // Note: going backwards! + for ( $i = $stackPtr; $i > $start; $i-- ) { + + // Ignore whitespace. + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // If this is a variable or array assignment, we've seen all we need to see. + if ( \T_VARIABLE === $this->tokens[ $i ]['code'] + || \T_CLOSE_SQUARE_BRACKET === $this->tokens[ $i ]['code'] + ) { + $needs_yoda = true; + break; + } + + // If this is a function call or something, we are OK. + if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] ) { + return; + } + } + + if ( ! $needs_yoda ) { + return; + } + + // Check if this is a var to var comparison, e.g.: if ( $var1 == $var2 ). + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + + if ( isset( Tokens::$castTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true ); + } + + if ( isset( Collections::ooHierarchyKeywords()[ $this->tokens[ $next_non_empty ]['code'] ] ) === true ) { + $next_non_empty = $this->phpcsFile->findNext( + ( Tokens::$emptyTokens + array( \T_DOUBLE_COLON => \T_DOUBLE_COLON ) ), + ( $next_non_empty + 1 ), + null, + true + ); + } + + if ( \T_VARIABLE === $this->tokens[ $next_non_empty ]['code'] ) { + return; + } + + $this->phpcsFile->addError( 'Use Yoda Condition checks, you must.', $stackPtr, 'NotYoda' ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/EscapeOutputSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/EscapeOutputSniff.php new file mode 100644 index 00000000..65ee5af3 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -0,0 +1,903 @@ + + */ + protected $unsafePrintingFunctions = array( + '_e' => array( + 'alternative' => 'esc_html_e() or esc_attr_e()', + 'params' => array( + 1 => 'text', + ), + ), + '_ex' => array( + 'alternative' => 'echo esc_html_x() or echo esc_attr_x()', + 'params' => array( + 1 => 'text', + ), + ), + ); + + /** + * List of names of the native PHP constants which can be considered safe. + * + * @since 1.0.0 + * + * @var array + */ + private $safe_php_constants = array( + 'PHP_EOL' => true, // String. + 'PHP_VERSION' => true, // Integer. + 'PHP_MAJOR_VERSION' => true, // Integer. + 'PHP_MINOR_VERSION' => true, // Integer. + 'PHP_RELEASE_VERSION' => true, // Integer. + 'PHP_VERSION_ID' => true, // Integer. + 'PHP_EXTRA_VERSION' => true, // String. + 'PHP_DEBUG' => true, // Integer. + ); + + /** + * List of tokens which can be considered as safe when directly part of the output. + * + * This list is enhanced with additional tokens in the `register()` method. + * + * @since 0.12.0 + * + * @var array + */ + private $safe_components = array( + \T_LNUMBER => \T_LNUMBER, + \T_DNUMBER => \T_DNUMBER, + \T_TRUE => \T_TRUE, + \T_FALSE => \T_FALSE, + \T_NULL => \T_NULL, + \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING, + \T_START_NOWDOC => \T_START_NOWDOC, + \T_NOWDOC => \T_NOWDOC, + \T_END_NOWDOC => \T_END_NOWDOC, + \T_BOOLEAN_NOT => \T_BOOLEAN_NOT, + ); + + /** + * List of keyword tokens this sniff listens for, which can also be used as an inline expression. + * + * @since 3.0.0 + * + * @var array + */ + private $target_keywords = array( + \T_EXIT => \T_EXIT, + \T_PRINT => \T_PRINT, + \T_THROW => \T_THROW, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return string|int[] + */ + public function register() { + // Enrich the list of "safe components" tokens. + $this->safe_components += Tokens::$comparisonTokens; + $this->safe_components += Tokens::$operators; + $this->safe_components += Tokens::$booleanOperators; + $this->safe_components += Collections::incrementDecrementOperators(); + + // Set up the tokens the sniff should listen too. + $targets = array_merge( parent::register(), $this->target_keywords ); + $targets[] = \T_ECHO; + $targets[] = \T_OPEN_TAG_WITH_ECHO; + + return $targets; + } + + /** + * Groups of functions this sniff is looking for. + * + * @since 3.0.0 + * + * @return array + */ + public function getGroups() { + // Make sure all array keys are lowercase (could contain user provided function names). + $printing_functions = array_change_key_case( $this->get_printing_functions(), \CASE_LOWER ); + + // Remove the unsafe printing functions to prevent duplicate notices. + $printing_functions = array_diff_key( $printing_functions, $this->unsafePrintingFunctions ); + + return array( + 'unsafe_printing_functions' => array( + 'functions' => array_keys( $this->unsafePrintingFunctions ), + ), + 'printing_functions' => array( + 'functions' => array_keys( $printing_functions ), + ), + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 3.0.0 This method has been split up. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + $start = ( $stackPtr + 1 ); + $end = $start; + + switch ( $this->tokens[ $stackPtr ]['code'] ) { + case \T_STRING: + // Prevent exclusion of any of the function groups. + $this->exclude = array(); + + // In the tests, custom printing functions may be added/removed on the fly. + if ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { + $this->setup_groups( 'functions' ); + } + + // Let the abstract parent class handle the initial function call check. + return parent::process_token( $stackPtr ); + + case \T_EXIT: + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false === $next_non_empty + || \T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] + || isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) === false + ) { + // Live coding/parse error or an exit/die which doesn't pass a status code. Ignore. + return; + } + + // $end is not examined, so make sure the parentheses are balanced. + $start = $next_non_empty; + $end = ( $this->tokens[ $next_non_empty ]['parenthesis_closer'] + 1 ); + break; + + case \T_THROW: + // Find the open parentheses, while stepping over the exception creation tokens. + $ignore = Tokens::$emptyTokens; + $ignore += Collections::namespacedNameTokens(); + $ignore += Collections::functionCallTokens(); + $ignore += Collections::objectOperators(); + + $next_relevant = $this->phpcsFile->findNext( $ignore, ( $stackPtr + 1 ), null, true ); + if ( false === $next_relevant ) { + return; + } + + if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) { + $next_relevant = $this->phpcsFile->findNext( $ignore, ( $next_relevant + 1 ), null, true ); + if ( false === $next_relevant ) { + return; + } + } + + if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $next_relevant ]['code'] + || isset( $this->tokens[ $next_relevant ]['parenthesis_closer'] ) === false + ) { + // Live codind/parse error or a pre-created exception. Nothing to do for us. + return; + } + + $end = $this->tokens[ $next_relevant ]['parenthesis_closer']; + + // Check if the throw is within a `try-catch`. + // Doing this here (instead of earlier) to allow skipping to the end of the statement. + $search_for = Collections::closedScopes(); + $search_for[ \T_TRY ] = \T_TRY; + + $last_condition = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, $search_for ); + if ( false !== $last_condition && \T_TRY === $this->tokens[ $last_condition ]['code'] ) { + // This exception will (probably) be caught, so ignore it. + return $end; + } + + $call_token = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $next_relevant - 1 ), null, true ); + $params = PassedParameters::getParameters( $this->phpcsFile, $call_token ); + if ( empty( $params ) ) { + // No parameters passed, nothing to do. + return $end; + } + + // Examine each parameter individually. + foreach ( $params as $param ) { + $this->check_code_is_escaped( $param['start'], ( $param['end'] + 1 ), 'ExceptionNotEscaped' ); + } + + return $end; + + case \T_PRINT: + $end = BCFile::findEndOfStatement( $this->phpcsFile, $stackPtr ); + if ( \T_COMMA !== $this->tokens[ $end ]['code'] + && \T_SEMICOLON !== $this->tokens[ $end ]['code'] + && \T_COLON !== $this->tokens[ $end ]['code'] + && \T_DOUBLE_ARROW !== $this->tokens[ $end ]['code'] + && isset( $this->tokens[ ( $end + 1 ) ] ) + ) { + /* + * FindEndOfStatement includes a comma/(semi-)colon/double arrow if that's the end of + * the statement, but for everything else, it returns the last non-empty token _before_ + * the end, which would mean the last non-empty token in the statement would not + * be examined. Let's fix that. + */ + ++$end; + } + + // Note: no need to check for close tag as close tag will have the token before the tag as the $end. + if ( $end >= ( $this->phpcsFile->numTokens - 1 ) ) { + $last_non_empty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end, null, true ); + if ( \T_SEMICOLON !== $this->tokens[ $last_non_empty ]['code'] ) { + // Live coding/parse error at end of file. Ignore. + return; + } + } + + // Special case for a print statement *within* a ternary, where we need to find the "inline else" as the end token. + $prev_non_empty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( \T_INLINE_THEN === $this->tokens[ $prev_non_empty ]['code'] ) { + $target_nesting_level = 0; + if ( empty( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) === false ) { + $target_nesting_level = \count( $this->tokens[ $stackPtr ]['nested_parenthesis'] ); + } + + $inline_else = false; + for ( $i = ( $stackPtr + 1 ); $i < $end; $i++ ) { + if ( \T_INLINE_ELSE !== $this->tokens[ $i ]['code'] ) { + continue; + } + + if ( empty( $this->tokens[ $i ]['nested_parenthesis'] ) + && 0 === $target_nesting_level + ) { + $inline_else = $i; + break; + } + + if ( empty( $this->tokens[ $i ]['nested_parenthesis'] ) === false + && \count( $this->tokens[ $i ]['nested_parenthesis'] ) === $target_nesting_level + ) { + $inline_else = $i; + break; + } + } + + if ( false === $inline_else ) { + // Live coding/parse error. Bow out. + return; + } + + $end = $inline_else; + } + + break; + + // Echo, open tag with echo. + default: + $end = $this->phpcsFile->findNext( array( \T_SEMICOLON, \T_CLOSE_TAG ), $stackPtr ); + if ( false === $end ) { + // Live coding/parse error. Bow out. + return; + } + + break; + } + + return $this->check_code_is_escaped( $start, $end ); + } + + /** + * Process a matched function call token. + * + * @since 3.0.0 Split off from the process_token() method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + // Make sure we only deal with actual function calls, not function import use statements. + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false === $next_non_empty + || \T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] + || isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) === false + ) { + // Live coding, parse error or not a function _call_. + return; + } + + $end = $this->tokens[ $next_non_empty ]['parenthesis_closer']; + + if ( 'unsafe_printing_functions' === $group_name ) { + $error = $this->phpcsFile->addError( + "All output should be run through an escaping function (like %s), found '%s'.", + $stackPtr, + 'UnsafePrintingFunction', + array( $this->unsafePrintingFunctions[ $matched_content ]['alternative'], $matched_content ) + ); + + // If the error was reported, don't bother checking the function's arguments. + if ( $error || empty( $this->unsafePrintingFunctions[ $matched_content ]['params'] ) ) { + return $end; + } + + // If the function was not reported for being unsafe, examine the relevant parameters. + $params = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + foreach ( $this->unsafePrintingFunctions[ $matched_content ]['params'] as $position => $name ) { + $param = PassedParameters::getParameterFromStack( $params, $position, $name ); + if ( false === $param ) { + // Parameter doesn't exist. Nothing to do. + continue; + } + + $this->check_code_is_escaped( $param['start'], ( $param['end'] + 1 ) ); + } + + return $end; + } + + $params = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + + /* + * These functions only need to have their first argument - `$message` - escaped. + * Note: user_error() is an alias for trigger_error(), so the param names are the same. + */ + if ( 'trigger_error' === $matched_content || 'user_error' === $matched_content ) { + $message_param = PassedParameters::getParameterFromStack( $params, 1, 'message' ); + if ( false === $message_param ) { + // Message parameter doesn't exist. Nothing to do. + return $end; + } + + return $this->check_code_is_escaped( $message_param['start'], ( $message_param['end'] + 1 ) ); + } + + /* + * If the first param to `_deprecated_file()` - `$file` - follows the typical `basename( __FILE__ )` + * pattern, it doesn't need to be escaped. + */ + if ( '_deprecated_file' === $matched_content ) { + $file_param = PassedParameters::getParameterFromStack( $params, 1, 'file' ); + + if ( false !== $file_param ) { + // Check for a particular code pattern which can safely be ignored. + if ( preg_match( '`^[\\\\]?basename\s*\(\s*__FILE__\s*\)$`', $file_param['clean'] ) === 1 ) { + unset( $params[1], $params['file'] ); // Remove the param, whether passed positionally or named. + } + } + unset( $file_param ); + } + + // Examine each parameter individually. + foreach ( $params as $param ) { + $this->check_code_is_escaped( $param['start'], ( $param['end'] + 1 ) ); + } + + return $end; + } + + /** + * Check whether each relevant part of an arbitrary group of token is output escaped. + * + * @since 3.0.0 Split off from the process_token() method. + * + * @param int $start The position to start checking from. + * @param int $end The position to stop the check at. + * @param string $code Code to use for the PHPCS error. + * + * @return int Integer stack pointer to skip forward. + */ + protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscaped' ) { + /* + * Check for a ternary operator. + * We only need to do this here if this statement is lacking parenthesis. + * Otherwise it will be handled in the below loop. + */ + $ternary = false; + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $start + 1 ), null, true ); + $last_non_empty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $end - 1 ), null, true ); + + if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] + || \T_CLOSE_PARENTHESIS !== $this->tokens[ $last_non_empty ]['code'] + || ( \T_OPEN_PARENTHESIS === $this->tokens[ $next_non_empty ]['code'] + && \T_CLOSE_PARENTHESIS === $this->tokens[ $last_non_empty ]['code'] + && isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) + && $this->tokens[ $next_non_empty ]['parenthesis_closer'] !== $last_non_empty + ) + ) { + // If there is a (long) ternary skip over the part before the ?. + $ternary = $this->find_long_ternary( $start, $end ); + if ( false !== $ternary ) { + $start = ( $ternary + 1 ); + } + } + + $in_cast = false; + $watch = true; + + // Looping through echo'd components. + for ( $i = $start; $i < $end; $i++ ) { + // Ignore whitespaces and comments. + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // Skip over irrelevant tokens. + if ( isset( Tokens::$magicConstants[ $this->tokens[ $i ]['code'] ] ) // Magic constants for debug functions. + || \T_NS_SEPARATOR === $this->tokens[ $i ]['code'] + || \T_DOUBLE_ARROW === $this->tokens[ $i ]['code'] + || \T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] + ) { + continue; + } + + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] ) { + if ( ! isset( $this->tokens[ $i ]['parenthesis_closer'] ) ) { + // Live coding or parse error. + break; + } + + if ( $in_cast ) { + // Skip to the end of a function call if it has been casted to a safe value. + $i = $this->tokens[ $i ]['parenthesis_closer']; + $in_cast = false; + + } else { + // Skip over the condition part of a (long) ternary (i.e., to after the ?). + $ternary = $this->find_long_ternary( ( $i + 1 ), $this->tokens[ $i ]['parenthesis_closer'] ); + if ( false !== $ternary ) { + $i = $ternary; + } + } + + continue; + } + + /* + * If a keyword is encountered in an inline expression and the keyword is one + * this sniff listens to, recurse into the sniff, handle the expression + * based on the keyword and skip over the code examined. + */ + if ( isset( $this->target_keywords[ $this->tokens[ $i ]['code'] ] ) ) { + $return_value = $this->process_token( $i ); + if ( isset( $return_value ) ) { + $i = $return_value; + } + continue; + } + + // Handle PHP 8.0+ match expressions. + if ( \T_MATCH === $this->tokens[ $i ]['code'] ) { + $match_valid = $this->walk_match_expression( $i, $code ); + if ( false === $match_valid ) { + // Live coding or parse error. Shouldn't be possible as PHP[CS] will tokenize the keyword as `T_STRING` in that case. + break; // @codeCoverageIgnore + } + + $i = $match_valid; + continue; + } + + // Examine the items in an array individually for array parameters. + if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $i ]['code'] ] ) ) { + $array_open_close = Arrays::getOpenClose( $this->phpcsFile, $i ); + if ( false === $array_open_close ) { + // Short list or misidentified short array token. + continue; + } + + $array_items = PassedParameters::getParameters( $this->phpcsFile, $i, 0, true ); + if ( ! empty( $array_items ) ) { + foreach ( $array_items as $array_item ) { + $this->check_code_is_escaped( $array_item['start'], ( $array_item['end'] + 1 ), $code ); + } + } + + $i = $array_open_close['closer']; + continue; + } + + // Ignore safe PHP native constants. + if ( \T_STRING === $this->tokens[ $i ]['code'] + && isset( $this->safe_php_constants[ $this->tokens[ $i ]['content'] ] ) + && ConstantsHelper::is_use_of_global_constant( $this->phpcsFile, $i ) + ) { + continue; + } + + // Wake up on concatenation characters, another part to check. + if ( \T_STRING_CONCAT === $this->tokens[ $i ]['code'] ) { + $watch = true; + continue; + } + + // Wake up after a ternary else (:). + if ( false !== $ternary && \T_INLINE_ELSE === $this->tokens[ $i ]['code'] ) { + $watch = true; + continue; + } + + // Wake up for commas. + if ( \T_COMMA === $this->tokens[ $i ]['code'] ) { + $in_cast = false; + $watch = true; + continue; + } + + if ( false === $watch ) { + continue; + } + + // Allow T_CONSTANT_ENCAPSED_STRING eg: echo 'Some String'; + // Also T_LNUMBER, e.g.: echo 45; exit -1; and booleans. + if ( isset( $this->safe_components[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // Check for use of *::class. + if ( \T_STRING === $this->tokens[ $i ]['code'] + || \T_VARIABLE === $this->tokens[ $i ]['code'] + || isset( Collections::ooHierarchyKeywords()[ $this->tokens[ $i ]['code'] ] ) + ) { + $double_colon = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), $end, true ); + if ( false !== $double_colon + && \T_DOUBLE_COLON === $this->tokens[ $double_colon ]['code'] + ) { + $class_keyword = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $double_colon + 1 ), $end, true ); + if ( false !== $class_keyword + && \T_STRING === $this->tokens[ $class_keyword ]['code'] + && 'class' === strtolower( $this->tokens[ $class_keyword ]['content'] ) + ) { + $i = $class_keyword; + continue; + } + } + } + + $watch = false; + + // Allow int/double/bool casted variables. + if ( isset( ContextHelper::get_safe_cast_tokens()[ $this->tokens[ $i ]['code'] ] ) ) { + /* + * If the next thing is a match expression, skip over it as whatever is + * being returned will be safe casted. + * Do not set `$in_cast` to `true`. + */ + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), $end, true ); + if ( false !== $next_non_empty + && \T_MATCH === $this->tokens[ $next_non_empty ]['code'] + && isset( $this->tokens[ $next_non_empty ]['scope_closer'] ) + ) { + $i = $this->tokens[ $next_non_empty ]['scope_closer']; + continue; + } + + $in_cast = true; + continue; + } + + // Handle heredocs separately as they only need escaping when interpolation is used. + if ( \T_START_HEREDOC === $this->tokens[ $i ]['code'] ) { + $current = ( $i + 1 ); + while ( isset( $this->tokens[ $current ] ) && \T_HEREDOC === $this->tokens[ $current ]['code'] ) { + $embeds = TextStrings::getEmbeds( $this->tokens[ $current ]['content'] ); + if ( ! empty( $embeds ) ) { + $this->phpcsFile->addError( + 'All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found interpolation in unescaped heredoc.', + $current, + 'HeredocOutputNotEscaped' + ); + } + ++$current; + } + + $i = $current; + continue; + } + + // Now check that next token is a function call. + if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + $ptr = $i; + $functionName = $this->tokens[ $i ]['content']; + $function_opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + $is_formatting_function = FormattingFunctionsHelper::is_formatting_function( $functionName ); + + if ( false !== $function_opener + && \T_OPEN_PARENTHESIS === $this->tokens[ $function_opener ]['code'] + ) { + if ( ArrayWalkingFunctionsHelper::is_array_walking_function( $functionName ) ) { + // Get the callback parameter. + $callback = ArrayWalkingFunctionsHelper::get_callback_parameter( $this->phpcsFile, $ptr ); + + if ( ! empty( $callback ) ) { + /* + * If this is a function callback (not a method callback array) and we're able + * to resolve the function name, do so. + */ + $mapped_function = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $callback['start'], + ( $callback['end'] + 1 ), + true + ); + + if ( false !== $mapped_function + && \T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $mapped_function ]['code'] + ) { + $functionName = TextStrings::stripQuotes( $this->tokens[ $mapped_function ]['content'] ); + $ptr = $mapped_function; + } + } + } + + // If this is a formatting function, we examine the parameters individually. + if ( $is_formatting_function ) { + $formatting_params = PassedParameters::getParameters( $this->phpcsFile, $i ); + if ( ! empty( $formatting_params ) ) { + foreach ( $formatting_params as $format_param ) { + $this->check_code_is_escaped( $format_param['start'], ( $format_param['end'] + 1 ), $code ); + } + } + + $watch = true; + } + + // Skip pointer to after the function. + if ( isset( $this->tokens[ $function_opener ]['parenthesis_closer'] ) ) { + $i = $this->tokens[ $function_opener ]['parenthesis_closer']; + } else { + // Live coding or parse error. + break; + } + } + + // If this is a safe function, we don't flag it. + if ( $is_formatting_function + || $this->is_escaping_function( $functionName ) + || $this->is_auto_escaped_function( $functionName ) + ) { + // Special case get_search_query() which is unsafe if $escaped = false. + if ( 'get_search_query' === strtolower( $functionName ) ) { + $escaped_param = PassedParameters::getParameter( $this->phpcsFile, $ptr, 1, 'escaped' ); + if ( false !== $escaped_param && 'true' !== $escaped_param['clean'] ) { + $this->phpcsFile->addError( + 'Output from get_search_query() is unsafe due to $escaped parameter being set to "false".', + $ptr, + 'UnsafeSearchQuery' + ); + } + } + + continue; + } + + $content = $functionName; + + } else { + $content = $this->tokens[ $i ]['content']; + $ptr = $i; + } + + // Make the error message a little more informative for array access variables. + if ( \T_VARIABLE === $this->tokens[ $ptr ]['code'] ) { + $array_keys = VariableHelper::get_array_access_keys( $this->phpcsFile, $ptr ); + + if ( ! empty( $array_keys ) ) { + $content .= '[' . implode( '][', $array_keys ) . ']'; + } + } + + $this->phpcsFile->addError( + "All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '%s'.", + $ptr, + $code, + array( $content ) + ); + } + + return $end; + } + + /** + * Check whether there is a ternary token at the right nesting level in an arbitrary set of tokens. + * + * @since 3.0.0 Split off from the process_token() method. + * + * @param int $start The position to start checking from. + * @param int $end The position to stop the check at. + * + * @return int|false Stack pointer to the ternary or FALSE if no ternary was found or + * if this is a short ternary. + */ + private function find_long_ternary( $start, $end ) { + for ( $i = $start; $i < $end; $i++ ) { + // Ignore anything within square brackets. + if ( isset( $this->tokens[ $i ]['bracket_opener'], $this->tokens[ $i ]['bracket_closer'] ) + && $i === $this->tokens[ $i ]['bracket_opener'] + ) { + $i = $this->tokens[ $i ]['bracket_closer']; + continue; + } + + // Skip past nested arrays, function calls and arbitrary groupings. + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] + && isset( $this->tokens[ $i ]['parenthesis_closer'] ) + ) { + $i = $this->tokens[ $i ]['parenthesis_closer']; + continue; + } + + // Skip past closures, anonymous classes and anything else scope related. + if ( isset( $this->tokens[ $i ]['scope_condition'], $this->tokens[ $i ]['scope_closer'] ) + && $this->tokens[ $i ]['scope_condition'] === $i + ) { + $i = $this->tokens[ $i ]['scope_closer']; + continue; + } + + if ( \T_INLINE_THEN !== $this->tokens[ $i ]['code'] ) { + continue; + } + + /* + * Okay, we found a ternary and it should be at the correct nesting level. + * If this is a short ternary, it shouldn't be ignored though. + */ + if ( Operators::isShortTernary( $this->phpcsFile, $i ) === true ) { + return false; + } + + return $i; + } + + return false; + } + + /** + * Examine a match expression and only check for escaping in the "returned" parts of the match expression. + * + * {@internal PHPCSUtils will likely contain a utility for parsing match expressions in the future. + * Ref: https://github.com/PHPCSStandards/PHPCSUtils/issues/497} + * + * @since 3.0.0 + * + * @param int $stackPtr Pointer to a T_MATCH token. + * @param string $code Code to use for the PHPCS error. + * + * @return int|false Stack pointer to skip to or FALSE if the match expression contained a parse error. + */ + private function walk_match_expression( $stackPtr, $code ) { + if ( ! isset( $this->tokens[ $stackPtr ]['scope_opener'], $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + // Parse error/live coding. Shouldn't be possible as PHP[CS] will tokenize the keyword as `T_STRING` in that case. + return false; // @codeCoverageIgnore + } + + $current = $this->tokens[ $stackPtr ]['scope_opener']; + $end = $this->tokens[ $stackPtr ]['scope_closer']; + do { + $current = $this->phpcsFile->findNext( \T_MATCH_ARROW, ( $current + 1 ), $end ); + if ( false === $current ) { + // We must have reached the last match item (or there is a parse error). + break; + } + + $item_start = ( $current + 1 ); + $item_end = false; + + // Find the first comma at the same level. + for ( $i = $item_start; $i <= $end; $i++ ) { + // Ignore anything within square brackets. + if ( isset( $this->tokens[ $i ]['bracket_opener'], $this->tokens[ $i ]['bracket_closer'] ) + && $i === $this->tokens[ $i ]['bracket_opener'] + ) { + $i = $this->tokens[ $i ]['bracket_closer']; + continue; + } + + // Skip past nested arrays, function calls and arbitrary groupings. + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] + && isset( $this->tokens[ $i ]['parenthesis_closer'] ) + ) { + $i = $this->tokens[ $i ]['parenthesis_closer']; + continue; + } + + // Skip past closures, anonymous classes and anything else scope related. + if ( isset( $this->tokens[ $i ]['scope_condition'], $this->tokens[ $i ]['scope_closer'] ) + && $this->tokens[ $i ]['scope_condition'] === $i + ) { + $i = $this->tokens[ $i ]['scope_closer']; + continue; + } + + if ( \T_COMMA !== $this->tokens[ $i ]['code'] + && $i !== $end + ) { + continue; + } + + $item_end = $i; + break; + } + + if ( false === $item_end ) { + // Parse error/live coding. Shouldn't be possible. + return false; // @codeCoverageIgnore + } + + // Now check that the value returned by this match "leaf" is correctly escaped. + $this->check_code_is_escaped( $item_start, $item_end, $code ); + + // Independently of whether or not the check was succesfull or ran into (parse error) problems, + // always skip to the identified end of the item. + $current = $item_end; + } while ( $current < $end ); + + return $end; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/NonceVerificationSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/NonceVerificationSniff.php new file mode 100644 index 00000000..02411473 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/NonceVerificationSniff.php @@ -0,0 +1,422 @@ + true, + '$_FILES' => true, + '$_GET' => false, + '$_REQUEST' => false, + ); + + /** + * Custom list of functions which verify nonces. + * + * @since 0.5.0 + * + * @var string[] + */ + public $customNonceVerificationFunctions = array(); + + /** + * List of the functions which verify nonces. + * + * @since 0.5.0 + * @since 0.11.0 Changed from public static to protected non-static. + * @since 3.0.0 - Moved from the generic `Sniff` class to this class. + * - Visibility changed from `protected` to `private. + * + * @var array + */ + private $nonceVerificationFunctions = array( + 'wp_verify_nonce' => true, + 'check_admin_referer' => true, + 'check_ajax_referer' => true, + ); + + /** + * Cache of previously added custom functions. + * + * Prevents having to do the same merges over and over again. + * + * @since 0.5.0 + * @since 0.11.0 - Changed from public static to protected non-static. + * - Changed the format from simple bool to array. + * @since 3.0.0 - Property rename from `$addedCustomFunctions` to `$addedCustomNonceFunctions`. + * - Visibility changed from `protected` to `private. + * - Format changed from a multi-dimensional array to a single-dimensional array. + * + * @var array + */ + private $addedCustomNonceFunctions = array(); + + /** + * Information on the all scopes that were checked to find a nonce verification in a particular file. + * + * The array will be in the following format: + * ``` + * array( + * 'file' => (string) The name of the file. + * 'cache' => (array) array( + * # => array( The key is the token pointer to the "start" position. + * 'end' => (int) The token pointer to the "end" position. + * 'nonce' => (int|bool) The token pointer where n nonce check + * was found, or false if none was found. + * ) + * ) + * ) + * ``` + * + * @since 3.0.0 + * + * @var array + */ + private $cached_results; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + $targets = array( \T_VARIABLE => \T_VARIABLE ); + $targets += Collections::listOpenTokensBC(); // We need to skip over lists. + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + // Skip over lists as whatever is in those will always be assignments. + if ( isset( Collections::listOpenTokensBC()[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $open_close = Lists::getOpenClose( $this->phpcsFile, $stackPtr ); + $skip_to = $stackPtr; + if ( false !== $open_close ) { + $skip_to = $open_close['closer']; + } + + return $skip_to; + } + + if ( ! isset( $this->superglobals[ $this->tokens[ $stackPtr ]['content'] ] ) ) { + return; + } + + if ( Scopes::isOOProperty( $this->phpcsFile, $stackPtr ) ) { + // Property with the same name as a superglobal. Not our target. + return; + } + + // Determine the cache keys for this item. + $cache_keys = array( + 'file' => $this->phpcsFile->getFilename(), + 'start' => 0, + 'end' => $stackPtr, + ); + + // If we're in a function, only look inside of it. + // This doesn't take arrow functions into account as those are "open". + $functionPtr = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, array( \T_FUNCTION, \T_CLOSURE ) ); + if ( false !== $functionPtr ) { + $cache_keys['start'] = $this->tokens[ $functionPtr ]['scope_opener']; + } + + $this->mergeFunctionLists(); + + $needs_nonce = $this->needs_nonce_check( $stackPtr, $cache_keys ); + if ( false === $needs_nonce ) { + return; + } + + if ( $this->has_nonce_check( $stackPtr, $cache_keys, ( 'after' === $needs_nonce ) ) ) { + return; + } + + // If we're still here, no nonce-verification function was found. + $error_code = 'Missing'; + if ( false === $this->superglobals[ $this->tokens[ $stackPtr ]['content'] ] ) { + $error_code = 'Recommended'; + } + + MessageHelper::addMessage( + $this->phpcsFile, + 'Processing form data without nonce verification.', + $stackPtr, + $this->superglobals[ $this->tokens[ $stackPtr ]['content'] ], + $error_code + ); + } + + /** + * Determine whether or not a nonce check is needed for the current superglobal. + * + * @since 3.0.0 + * + * @param int $stackPtr The position of the current token in the stack of tokens. + * @param array $cache_keys The keys for the applicable cache (to potentially set). + * + * @return string|false String "before" or "after" if a nonce check is needed. + * FALSE when no nonce check is needed. + */ + protected function needs_nonce_check( $stackPtr, array $cache_keys ) { + $in_nonce_check = ContextHelper::is_in_function_call( $this->phpcsFile, $stackPtr, $this->nonceVerificationFunctions ); + if ( false !== $in_nonce_check ) { + // This *is* the nonce check, so bow out, but do store to cache. + // @todo Change to use arg unpacking once PHP < 5.6 has been dropped. + $this->set_cache( $cache_keys['file'], $cache_keys['start'], $cache_keys['end'], $in_nonce_check ); + return false; + } + + if ( Context::inUnset( $this->phpcsFile, $stackPtr ) ) { + // Variable is only being unset, no nonce check needed. + return false; + } + + if ( VariableHelper::is_assignment( $this->phpcsFile, $stackPtr, false ) ) { + // Overwriting the value of a superglobal. + return false; + } + + $needs_nonce = 'before'; + if ( ContextHelper::is_in_isset_or_empty( $this->phpcsFile, $stackPtr ) + || ContextHelper::is_in_type_test( $this->phpcsFile, $stackPtr ) + || VariableHelper::is_comparison( $this->phpcsFile, $stackPtr ) + || VariableHelper::is_assignment( $this->phpcsFile, $stackPtr, true ) + || ContextHelper::is_in_array_comparison( $this->phpcsFile, $stackPtr ) + || ContextHelper::is_in_function_call( $this->phpcsFile, $stackPtr, UnslashingFunctionsHelper::get_functions() ) !== false + || $this->is_only_sanitized( $this->phpcsFile, $stackPtr ) + ) { + $needs_nonce = 'after'; + } + + return $needs_nonce; + } + + /** + * Check if this token has an associated nonce check. + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the generic `Sniff` class to this class. + * - Visibility changed from `protected` to `private. + * - New `$cache_keys` parameter. + * - New `$allow_nonce_after` parameter. + * + * @param int $stackPtr The position of the current token in the stack of tokens. + * @param array $cache_keys The keys for the applicable cache. + * @param bool $allow_nonce_after Whether the nonce check _must_ be before the $stackPtr or + * is allowed _after_ the $stackPtr. + * + * @return bool + */ + private function has_nonce_check( $stackPtr, array $cache_keys, $allow_nonce_after = false ) { + $start = $cache_keys['start']; + $end = $cache_keys['end']; + + // We allow for certain actions, such as an isset() check to come before the nonce check. + // If this superglobal is inside such a check, look for the nonce after it as well, + // all the way to the end of the scope. + if ( true === $allow_nonce_after ) { + $end = ( 0 === $start ) ? $this->phpcsFile->numTokens : $this->tokens[ $start ]['scope_closer']; + } + + // Check against the cache. + $current_cache = $this->get_cache( $cache_keys['file'], $start ); + if ( false !== $current_cache['nonce'] ) { + // If we have already found a nonce check in this scope, we just + // need to check whether it comes before this token. It is OK if the + // check is after the token though, if this was only an isset() check. + return ( true === $allow_nonce_after || $current_cache['nonce'] < $stackPtr ); + } elseif ( $end <= $current_cache['end'] ) { + // If not, we can still go ahead and return false if we've already + // checked to the end of the search area. + return false; + } + + $search_start = $start; + if ( $current_cache['end'] > $start ) { + // We haven't checked this far yet, but we can still save work by + // skipping over the part we've already checked. + $search_start = $this->cached_results['cache'][ $start ]['end']; + } + + // Loop through the tokens looking for nonce verification functions. + for ( $i = $search_start; $i < $end; $i++ ) { + // Skip over nested closed scope constructs. + if ( isset( Collections::closedScopes()[ $this->tokens[ $i ]['code'] ] ) + || \T_FN === $this->tokens[ $i ]['code'] + ) { + if ( isset( $this->tokens[ $i ]['scope_closer'] ) ) { + $i = $this->tokens[ $i ]['scope_closer']; + } + continue; + } + + // If this isn't a function name, skip it. + if ( \T_STRING !== $this->tokens[ $i ]['code'] ) { + continue; + } + + // If this is one of the nonce verification functions, we can bail out. + if ( isset( $this->nonceVerificationFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + /* + * Now, make sure it is a call to a global function. + */ + if ( ContextHelper::has_object_operator_before( $this->phpcsFile, $i ) === true ) { + continue; + } + + if ( ContextHelper::is_token_namespaced( $this->phpcsFile, $i ) === true ) { + continue; + } + + $this->set_cache( $cache_keys['file'], $start, $end, $i ); + return true; + } + } + + // We're still here, so no luck. + $this->set_cache( $cache_keys['file'], $start, $end, false ); + + return false; + } + + /** + * Helper function to retrieve results from the cache. + * + * @since 3.0.0 + * + * @param string $filename The name of the current file. + * @param int $start The stack pointer searches started from. + * + * @return array + */ + private function get_cache( $filename, $start ) { + if ( is_array( $this->cached_results ) + && $filename === $this->cached_results['file'] + && isset( $this->cached_results['cache'][ $start ] ) + ) { + return $this->cached_results['cache'][ $start ]; + } + + return array( + 'end' => 0, + 'nonce' => false, + ); + } + + /** + * Helper function to store results to the cache. + * + * @since 3.0.0 + * + * @param string $filename The name of the current file. + * @param int $start The stack pointer searches started from. + * @param int $end The stack pointer searched stopped at. + * @param int|bool $nonce Stack pointer to the nonce verification function call or false if none was found. + * + * @return void + */ + private function set_cache( $filename, $start, $end, $nonce ) { + if ( is_array( $this->cached_results ) === false + || $filename !== $this->cached_results['file'] + ) { + $this->cached_results = array( + 'file' => $filename, + 'cache' => array( + $start => array( + 'end' => $end, + 'nonce' => $nonce, + ), + ), + ); + return; + } + + // Okay, so we know the current cache is for the current file. Check if we've seen this start pointer before. + if ( isset( $this->cached_results['cache'][ $start ] ) === false ) { + $this->cached_results['cache'][ $start ] = array( + 'end' => $end, + 'nonce' => $nonce, + ); + return; + } + + // Update existing entry. + if ( $end > $this->cached_results['cache'][ $start ]['end'] ) { + $this->cached_results['cache'][ $start ]['end'] = $end; + } + + $this->cached_results['cache'][ $start ]['nonce'] = $nonce; + } + + /** + * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. + * + * @since 0.11.0 Split out from the `process()` method. + * + * @return void + */ + protected function mergeFunctionLists() { + if ( $this->customNonceVerificationFunctions !== $this->addedCustomNonceFunctions ) { + $this->nonceVerificationFunctions = RulesetPropertyHelper::merge_custom_array( + $this->customNonceVerificationFunctions, + $this->nonceVerificationFunctions + ); + + $this->addedCustomNonceFunctions = $this->customNonceVerificationFunctions; + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/PluginMenuSlugSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/PluginMenuSlugSniff.php new file mode 100644 index 00000000..64711da2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/PluginMenuSlugSniff.php @@ -0,0 +1,126 @@ +> Key is the name of the functions being targetted. + * Value is an array with parameter positions as the + * keys and parameter names as the values + */ + protected $target_functions = array( + 'add_comments_page' => array( + 4 => 'menu_slug', + ), + 'add_dashboard_page' => array( + 4 => 'menu_slug', + ), + 'add_links_page' => array( + 4 => 'menu_slug', + ), + 'add_management_page' => array( + 4 => 'menu_slug', + ), + 'add_media_page' => array( + 4 => 'menu_slug', + ), + 'add_menu_page' => array( + 4 => 'menu_slug', + ), + 'add_object_page' => array( + 4 => 'menu_slug', + ), + 'add_options_page' => array( + 4 => 'menu_slug', + ), + 'add_pages_page' => array( + 4 => 'menu_slug', + ), + 'add_plugins_page' => array( + 4 => 'menu_slug', + ), + 'add_posts_page' => array( + 4 => 'menu_slug', + ), + 'add_submenu_page' => array( + 1 => 'parent_slug', + 5 => 'menu_slug', + ), + 'add_theme_page' => array( + 4 => 'menu_slug', + ), + 'add_users_page' => array( + 4 => 'menu_slug', + ), + 'add_utility_page' => array( + 4 => 'menu_slug', + ), + ); + + /** + * Process the parameters of a matched function. + * + * @since 0.11.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + foreach ( $this->target_functions[ $matched_content ] as $position => $param_name ) { + $found_param = PassedParameters::getParameterFromStack( $parameters, $position, $param_name ); + if ( false === $found_param ) { + continue; + } + + $file_constant = $this->phpcsFile->findNext( \T_FILE, $found_param['start'], ( $found_param['end'] + 1 ) ); + if ( false !== $file_constant ) { + $this->phpcsFile->addWarning( 'Using __FILE__ for menu slugs risks exposing filesystem structure.', $file_constant, 'Using__FILE__' ); + } + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/SafeRedirectSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/SafeRedirectSniff.php new file mode 100644 index 00000000..88a08313 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/SafeRedirectSniff.php @@ -0,0 +1,45 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'wp_redirect' => array( + 'type' => 'warning', + 'message' => '%s() found. Using wp_safe_redirect(), along with the "allowed_redirect_hosts" filter if needed, can help avoid any chances of malicious redirects within code. It is also important to remember to call exit() after a redirect so that no other unwanted code is executed.', + 'functions' => array( + 'wp_redirect', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php new file mode 100644 index 00000000..6f46261b --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php @@ -0,0 +1,244 @@ + + */ + private $slashed_superglobals = array( + '$_COOKIE' => true, + '$_GET' => true, + '$_POST' => true, + '$_REQUEST' => true, + '$_SERVER' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + \T_VARIABLE, + \T_DOUBLE_QUOTED_STRING, + \T_HEREDOC, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + // Handling string interpolation. + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $stackPtr ]['code'] + || \T_HEREDOC === $this->tokens[ $stackPtr ]['code'] + ) { + // Retrieve all embeds, but use only the initial variable name part. + $interpolated_variables = array_map( + static function ( $embed ) { + return preg_replace( '`^(\{?\$\{?\(?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)(.*)$`', '$2', $embed ); + }, + TextStrings::getEmbeds( $this->tokens[ $stackPtr ]['content'] ) + ); + + // Filter the embeds down to superglobals only. + $interpolated_superglobals = array_filter( + $interpolated_variables, + static function ( $var_name ) { + return ( 'GLOBALS' !== $var_name && Variables::isSuperglobalName( $var_name ) ); + } + ); + + foreach ( $interpolated_superglobals as $bad_variable ) { + $this->phpcsFile->addError( 'Detected usage of a non-sanitized, non-validated input variable %s: %s', $stackPtr, 'InputNotValidatedNotSanitized', array( $bad_variable, $this->tokens[ $stackPtr ]['content'] ) ); + } + + return; + } + + /* Handle variables */ + + // Check if this is a superglobal we want to examine. + if ( '$GLOBALS' === $this->tokens[ $stackPtr ]['content'] + || Variables::isSuperglobalName( $this->tokens[ $stackPtr ]['content'] ) === false + ) { + return; + } + + // If the variable is being unset, we don't care about it. + if ( Context::inUnset( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // If we're overriding a superglobal with an assignment, no need to test. + if ( VariableHelper::is_assignment( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // This superglobal is being validated. + if ( ContextHelper::is_in_isset_or_empty( $this->phpcsFile, $stackPtr ) ) { + return; + } + + $array_keys = VariableHelper::get_array_access_keys( $this->phpcsFile, $stackPtr ); + + if ( empty( $array_keys ) ) { + return; + } + + $error_data = array( $this->tokens[ $stackPtr ]['content'] . '[' . implode( '][', $array_keys ) . ']' ); + + /* + * Check for validation first. + */ + $validated = false; + + for ( $i = ( $stackPtr + 1 ); $i < $this->phpcsFile->numTokens; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + if ( \T_OPEN_SQUARE_BRACKET === $this->tokens[ $i ]['code'] + && isset( $this->tokens[ $i ]['bracket_closer'] ) + ) { + // Skip over array keys. + $i = $this->tokens[ $i ]['bracket_closer']; + continue; + } + + if ( \T_COALESCE === $this->tokens[ $i ]['code'] ) { + $validated = true; + } + + // Anything else means this is not a validation coalesce. + break; + } + + if ( false === $validated ) { + $validated = ValidationHelper::is_validated( $this->phpcsFile, $stackPtr, $array_keys, $this->check_validation_in_scope_only ); + } + + if ( false === $validated ) { + $this->phpcsFile->addError( + 'Detected usage of a possibly undefined superglobal array index: %s. Use isset() or empty() to check the index exists before using it', + $stackPtr, + 'InputNotValidated', + $error_data + ); + } + + // If this variable is being tested with one of the `is_..()` functions, sanitization isn't needed. + if ( ContextHelper::is_in_type_test( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // If this is a comparison ('a' == $_POST['foo']), sanitization isn't needed. + if ( VariableHelper::is_comparison( $this->phpcsFile, $stackPtr, false ) ) { + return; + } + + // If this is a comparison using the array comparison functions, sanitization isn't needed. + if ( ContextHelper::is_in_array_comparison( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // Now look for sanitizing functions. + if ( ! $this->is_sanitized( $this->phpcsFile, $stackPtr, array( $this, 'add_unslash_error' ) ) ) { + $this->phpcsFile->addError( + 'Detected usage of a non-sanitized input variable: %s', + $stackPtr, + 'InputNotSanitized', + $error_data + ); + } + } + + /** + * Add an error for missing use of unslashing. + * + * @since 0.5.0 + * @since 3.0.0 - Moved from the `Sniff` class to this class. + * - The `$phpcsFile` parameter was added. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack + * which is missing unslashing. + * + * @return void + */ + public function add_unslash_error( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + $var_name = $tokens[ $stackPtr ]['content']; + + if ( isset( $this->slashed_superglobals[ $var_name ] ) === false ) { + // WP doesn't slash these, so they don't need unslashing. + return; + } + + // We know there will be array keys as that's checked in the process_token() method. + $array_keys = VariableHelper::get_array_access_keys( $phpcsFile, $stackPtr ); + $error_data = array( $var_name . '[' . implode( '][', $array_keys ) . ']' ); + + $phpcsFile->addError( + '%s not unslashed before sanitization. Use wp_unslash() or similar', + $stackPtr, + 'MissingUnslash', + $error_data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php new file mode 100644 index 00000000..d87f338d --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php @@ -0,0 +1,862 @@ +> Function name as key, array with target + * parameter and name as value. + */ + protected $target_functions = array( + 'load_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'load_plugin_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'load_muplugin_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'load_theme_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'load_child_theme_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'load_script_textdomain' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'unload_textdomain' => array( + 'position' => 1, + 'name' => 'domain', + ), + + '__' => array( + 'position' => 2, + 'name' => 'domain', + ), + '_e' => array( + 'position' => 2, + 'name' => 'domain', + ), + '_x' => array( + 'position' => 3, + 'name' => 'domain', + ), + '_ex' => array( + 'position' => 3, + 'name' => 'domain', + ), + '_n' => array( + 'position' => 4, + 'name' => 'domain', + ), + '_nx' => array( + 'position' => 5, + 'name' => 'domain', + ), + '_n_noop' => array( + 'position' => 3, + 'name' => 'domain', + ), + '_nx_noop' => array( + 'position' => 4, + 'name' => 'domain', + ), + 'translate_nooped_plural' => array( + 'position' => 3, + 'name' => 'domain', + ), + + 'esc_html__' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'esc_html_e' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'esc_html_x' => array( + 'position' => 3, + 'name' => 'domain', + ), + 'esc_attr__' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'esc_attr_e' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'esc_attr_x' => array( + 'position' => 3, + 'name' => 'domain', + ), + + 'is_textdomain_loaded' => array( + 'position' => 1, + 'name' => 'domain', + ), + 'get_translations_for_domain' => array( + 'position' => 1, + 'name' => 'domain', + ), + + // Deprecated functions. + '_c' => array( + 'position' => 2, + 'name' => 'domain', + ), + '_nc' => array( + 'position' => 4, + 'name' => 'domain', + ), + '__ngettext' => array( + 'position' => 4, + 'name' => 'domain', + ), + '__ngettext_noop' => array( + 'position' => 3, + 'name' => 'domain', + ), + 'translate_with_context' => array( + 'position' => 2, + 'name' => 'domain', + ), + + // Shouldn't be used by plugins/themes. + 'translate' => array( + 'position' => 2, + 'name' => 'domain', + ), + 'translate_with_gettext_context' => array( + 'position' => 3, + 'name' => 'domain', + ), + + // WP private functions. Shouldn't be used by plugins/themes. + '_load_textdomain_just_in_time' => array( + 'position' => 1, + 'name' => 'domain', + ), + '_get_path_to_translation_from_lang_dir' => array( + 'position' => 1, + 'name' => 'domain', + ), + '_get_path_to_translation' => array( + 'position' => 1, + 'name' => 'domain', + ), + ); + + /** + * Whether a valid new text domain was found. + * + * @since 1.2.0 + * + * @var bool + */ + private $is_valid = false; + + /** + * The new text domain as validated. + * + * @since 1.2.0 + * + * @var string + */ + private $validated_textdomain = ''; + + /** + * Whether the plugin/theme header has been seen and fixed yet. + * + * @since 1.2.0 + * + * @var bool + */ + private $header_found = false; + + /** + * Possible headers for a theme. + * + * @link https://developer.wordpress.org/themes/basics/main-stylesheet-style-css/ + * + * @since 1.2.0 + * + * @var array Array key is the header name, the value indicated whether it is a + * required (true) or optional (false) header. + */ + private $theme_headers = array( + 'Theme Name' => true, + 'Theme URI' => false, + 'Author' => true, + 'Author URI' => false, + 'Description' => true, + 'Version' => true, + 'Requires at least' => true, + 'Tested up to' => true, + 'Requires PHP' => true, + 'License' => true, + 'License URI' => true, + 'Text Domain' => true, + 'Tags' => false, + 'Domain Path' => false, + ); + + /** + * Possible headers for a plugin. + * + * @link https://developer.wordpress.org/plugins/plugin-basics/header-requirements/ + * + * @since 1.2.0 + * + * @var array Array key is the header name, the value indicated whether it is a + * required (true) or optional (false) header. + */ + private $plugin_headers = array( + 'Plugin Name' => true, + 'Plugin URI' => false, + 'Description' => false, + 'Version' => false, + 'Requires at least' => false, + 'Requires PHP' => false, + 'Author' => false, + 'Author URI' => false, + 'License' => false, + 'License URI' => false, + 'Text Domain' => false, + 'Domain Path' => false, + 'Network' => false, + 'Update URI' => false, + ); + + /** + * Regex template to match theme/plugin headers. + * + * @since 1.2.0 + * + * @var string + */ + private $header_regex_template = '`^(?:\s*(?:(?:\*|//)\s*)?)?(%s)\s*:\s*([^\r\n]+)`'; + + /** + * Regex to match theme headers. + * + * Set from within the register() method. + * + * @since 1.2.0 + * + * @var string + */ + private $theme_header_regex; + + /** + * Regex to match plugin headers. + * + * Set from within the register() method. + * + * @since 1.2.0 + * + * @var string + */ + private $plugin_header_regex; + + /** + * The --tab-width CLI value that is being used. + * + * @since 1.2.0 + * + * @var int + */ + private $tab_width = null; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.2.0 + * + * @return array + */ + public function register() { + $headers = array_map( + 'preg_quote', + array_keys( $this->theme_headers ), + array_fill( 0, \count( $this->theme_headers ), '`' ) + ); + $this->theme_header_regex = sprintf( $this->header_regex_template, implode( '|', $headers ) ); + + $headers = array_map( + 'preg_quote', + array_keys( $this->plugin_headers ), + array_fill( 0, \count( $this->plugin_headers ), '`' ) + ); + $this->plugin_header_regex = sprintf( $this->header_regex_template, implode( '|', $headers ) ); + + $targets = parent::register(); + + $targets[] = \T_DOC_COMMENT_OPEN_TAG; + $targets[] = \T_COMMENT; + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + // Check if the old/new properties are correctly set. If not, bow out. + if ( ! is_string( $this->new_text_domain ) + || '' === $this->new_text_domain + ) { + return ( $this->phpcsFile->numTokens + 1 ); + } + + if ( isset( $this->old_text_domain ) ) { + $this->old_text_domain = RulesetPropertyHelper::merge_custom_array( $this->old_text_domain, array(), false ); + + if ( ! is_array( $this->old_text_domain ) + || array() === $this->old_text_domain + ) { + return ( $this->phpcsFile->numTokens + 1 ); + } + } + + // Only validate and throw warning about the text domain once. + if ( $this->new_text_domain !== $this->validated_textdomain ) { + $this->is_valid = false; + $this->validated_textdomain = $this->new_text_domain; + $this->header_found = false; + + if ( 'default' === $this->new_text_domain ) { + $this->phpcsFile->addWarning( + 'The "default" text domain is reserved for WordPress core use and should not be used by plugins or themes', + 0, + 'ReservedNewDomain', + array( $this->new_text_domain ) + ); + + return ( $this->phpcsFile->numTokens + 1 ); + } + + if ( preg_match( '`^[a-z0-9-]+$`', $this->new_text_domain ) !== 1 ) { + $this->phpcsFile->addWarning( + 'The text domain should be a simple lowercase text string with words separated by dashes. "%s" appears invalid', + 0, + 'InvalidNewDomain', + array( $this->new_text_domain ) + ); + + return ( $this->phpcsFile->numTokens + 1 ); + } + + // If the text domain passed both validations, it should be considered valid. + $this->is_valid = true; + + } elseif ( false === $this->is_valid ) { + return ( $this->phpcsFile->numTokens + 1 ); + } + + if ( isset( $this->tab_width ) === false ) { + $this->tab_width = Helper::getTabWidth( $this->phpcsFile ); + } + + if ( \T_DOC_COMMENT_OPEN_TAG === $this->tokens[ $stackPtr ]['code'] + || \T_COMMENT === $this->tokens[ $stackPtr ]['code'] + ) { + // Examine for plugin/theme file header. + return $this->process_comments( $stackPtr ); + + } elseif ( isset( $this->phpcsFile->tokenizerType ) === false || 'CSS' !== $this->phpcsFile->tokenizerType ) { + // Examine a T_STRING token in a PHP file as a function call. + return parent::process_token( $stackPtr ); + } + } + + + /** + * Process the parameters of a matched function. + * + * @since 1.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $target_param_specs = $this->target_functions[ $matched_content ]; + $found_param = PassedParameters::getParameterFromStack( $parameters, $target_param_specs['position'], $target_param_specs['name'] ); + + if ( false === $found_param && 1 !== $target_param_specs['position'] ) { + $error_msg = 'Missing $domain arg'; + $error_code = 'MissingArgDomain'; + + $has_named_params = false; + foreach ( $parameters as $param ) { + if ( isset( $param['name'] ) ) { + $has_named_params = true; + break; + } + } + + if ( false === $has_named_params && isset( $parameters[ ( $target_param_specs['position'] - 1 ) ] ) ) { + $fix = $this->phpcsFile->addFixableError( $error_msg, $stackPtr, $error_code ); + + if ( true === $fix ) { + $start_previous = $parameters[ ( $target_param_specs['position'] - 1 ) ]['start']; + $end_previous = $parameters[ ( $target_param_specs['position'] - 1 ) ]['end']; + if ( \T_WHITESPACE === $this->tokens[ $start_previous ]['code'] + && $this->tokens[ $start_previous ]['content'] === $this->phpcsFile->eolChar + ) { + // Replicate the new line + indentation of the previous item. + $replacement = ','; + for ( $i = $start_previous; $i <= $end_previous; $i++ ) { + if ( \T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + break; + } + + if ( isset( $this->tokens[ $i ]['orig_content'] ) ) { + $replacement .= $this->tokens[ $i ]['orig_content']; + } else { + $replacement .= $this->tokens[ $i ]['content']; + } + } + + $replacement .= "'{$this->new_text_domain}'"; + } else { + $replacement = ", '{$this->new_text_domain}'"; + } + + if ( \T_WHITESPACE === $this->tokens[ $end_previous ]['code'] ) { + $this->phpcsFile->fixer->addContentBefore( $end_previous, $replacement ); + } else { + $this->phpcsFile->fixer->addContent( $end_previous, $replacement ); + } + } + } elseif ( true === $has_named_params ) { + /* + * Function call using named arguments. For now, we will not auto-fix this. + * + * {@internal If we don't bother with indentation and such, this can be made + * auto-fixable by getting the 'end' of the last seen parameter and adding the + * domain parameter, with the 'domain: ' parameter label, after the last + * seen parameter.} + */ + $this->phpcsFile->addError( $error_msg, $stackPtr, $error_code ); + } else { + $error_msg .= ' and preceding argument(s)'; + $error_code = 'MissingArgs'; + + // Expected preceeding param also missing, just throw the warning. + $this->phpcsFile->addWarning( $error_msg, $stackPtr, $error_code ); + } + + return; + } + + // Target parameter found. Let's examine it. + $domain_param_start = $found_param['start']; + $domain_param_end = $found_param['end']; + $domain_token = null; + + for ( $i = $domain_param_start; $i <= $domain_param_end; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + if ( \T_CONSTANT_ENCAPSED_STRING !== $this->tokens[ $i ]['code'] ) { + // Unexpected token found, not our concern. This is handled by the I18n sniff. + return; + } + + if ( isset( $domain_token ) ) { + // More than one T_CONSTANT_ENCAPSED_STRING found, not our concern. This is handled by the I18n sniff. + return; + } + + $domain_token = $i; + } + + // If we're still here, this means only one T_CONSTANT_ENCAPSED_STRING was found. + $old_domain = TextStrings::stripQuotes( $this->tokens[ $domain_token ]['content'] ); + + if ( ! \in_array( $old_domain, $this->old_text_domain, true ) ) { + // Not a text domain targetted for replacement, ignore. + return; + } + + $fix = $this->phpcsFile->addFixableError( + 'Mismatched text domain. Expected \'%s\' but found \'%s\'', + $domain_token, + 'TextDomainMismatch', + array( $this->new_text_domain, $old_domain ) + ); + + if ( true === $fix ) { + $replacement = str_replace( $old_domain, $this->new_text_domain, $this->tokens[ $domain_token ]['content'] ); + $this->phpcsFile->fixer->replaceToken( $domain_token, $replacement ); + } + } + + /** + * Process the function if no parameters were found. + * + * @since 1.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return void + */ + public function process_no_parameters( $stackPtr, $group_name, $matched_content ) { + $target_param = $this->target_functions[ $matched_content ]; + + if ( 1 !== $target_param['position'] ) { + // Only process the no param case as fixable if the text domain is expected to be the first parameter. + $this->phpcsFile->addWarning( 'Missing $domain arg and preceding argument(s)', $stackPtr, 'MissingArgs' ); + return; + } + + $opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $opener ]['code'] + || isset( $this->tokens[ $opener ]['parenthesis_closer'] ) === false + ) { + // Parse error or live coding. + return; + } + + $fix = $this->phpcsFile->addFixableError( 'Missing $domain arg', $stackPtr, 'MissingArgDomain' ); + if ( true === $fix ) { + $closer = $this->tokens[ $opener ]['parenthesis_closer']; + $replacement = " '{$this->new_text_domain}' "; + + if ( $this->tokens[ $opener ]['line'] !== $this->tokens[ $closer ]['line'] ) { + $replacement = trim( $replacement ); + $addBefore = ( $closer - 1 ); + if ( \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code'] + && $this->tokens[ $closer - 1 ]['line'] === $this->tokens[ $closer ]['line'] + ) { + if ( isset( $this->tokens[ ( $closer - 1 ) ]['orig_content'] ) ) { + $replacement = $this->tokens[ ( $closer - 1 ) ]['orig_content'] + . "\t" + . $replacement; + } else { + $replacement = $this->tokens[ ( $closer - 1 ) ]['content'] + . str_repeat( ' ', $this->tab_width ) + . $replacement; + } + + --$addBefore; + } else { + // We don't know whether the code uses tabs or spaces, so presume WPCS, i.e. tabs. + $replacement = "\t" . $replacement; + } + + $replacement = $this->phpcsFile->eolChar . $replacement; + + $this->phpcsFile->fixer->addContentBefore( $addBefore, $replacement ); + + } elseif ( \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code'] ) { + $this->phpcsFile->fixer->replaceToken( ( $closer - 1 ), $replacement ); + } else { + $this->phpcsFile->fixer->addContentBefore( $closer, $replacement ); + } + } + } + + + /** + * Process comments to find the plugin/theme headers. + * + * @since 1.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_comments( $stackPtr ) { + if ( true === $this->header_found && ! defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { + return; + } + + $regex = $this->plugin_header_regex; + $headers = $this->plugin_headers; + $type = 'plugin'; + $skip_to = $stackPtr; + + $file = TextStrings::stripQuotes( $this->phpcsFile->getFileName() ); + if ( 'STDIN' === $file ) { + return; + } + + $file_name = basename( $file ); + if ( isset( $this->phpcsFile->tokenizerType ) && 'CSS' === $this->phpcsFile->tokenizerType ) { + if ( 'style.css' !== $file_name && ! defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { + // CSS files only need to be examined for the file header. + return ( $this->phpcsFile->numTokens + 1 ); + } + + $regex = $this->theme_header_regex; + $headers = $this->theme_headers; + $type = 'theme'; + } + + $comment_details = array( + 'required_header_found' => false, + 'headers_found' => 0, + 'text_domain_ptr' => false, + 'text_domain_found' => '', + 'last_header_ptr' => false, + 'last_header_matches' => array(), + ); + + if ( \T_COMMENT === $this->tokens[ $stackPtr ]['code'] ) { + $block_comment = false; + if ( substr( $this->tokens[ $stackPtr ]['content'], 0, 2 ) === '/*' ) { + $block_comment = true; + } + + $current = $stackPtr; + do { + if ( false === $comment_details['text_domain_ptr'] + || false === $comment_details['required_header_found'] + || $comment_details['headers_found'] < 3 + ) { + $comment_details = $this->examine_comment_line( $current, $regex, $headers, $comment_details ); + } + + if ( true === $block_comment && substr( $this->tokens[ $current ]['content'], -2 ) === '*/' ) { + ++$current; + break; + } + + ++$current; + } while ( isset( $this->tokens[ $current ] ) && \T_COMMENT === $this->tokens[ $current ]['code'] ); + + $skip_to = $current; + + } else { + if ( ! isset( $this->tokens[ $stackPtr ]['comment_closer'] ) ) { + return; + } + + $closer = $this->tokens[ $stackPtr ]['comment_closer']; + $current = $stackPtr; + + while ( ( $current = $this->phpcsFile->findNext( \T_DOC_COMMENT_STRING, ( $current + 1 ), $closer ) ) !== false ) { + $comment_details = $this->examine_comment_line( $current, $regex, $headers, $comment_details ); + + if ( false !== $comment_details['text_domain_ptr'] + && true === $comment_details['required_header_found'] + && $comment_details['headers_found'] >= 3 + ) { + // No need to look at the rest of the docblock. + break; + } + } + + $skip_to = $closer; + } + + // So, was this the plugin/theme header ? + if ( true === $comment_details['required_header_found'] + && $comment_details['headers_found'] >= 3 + ) { + $this->header_found = true; + + $text_domain_ptr = $comment_details['text_domain_ptr']; + $text_domain_found = $comment_details['text_domain_found']; + + if ( false !== $text_domain_ptr ) { + if ( $this->new_text_domain !== $text_domain_found + && ( \in_array( $text_domain_found, $this->old_text_domain, true ) ) + ) { + $fix = $this->phpcsFile->addFixableError( + 'Mismatched text domain in %s header. Expected \'%s\' but found \'%s\'', + $text_domain_ptr, + 'TextDomainHeaderMismatch', + array( + $type, + $this->new_text_domain, + $text_domain_found, + ) + ); + + if ( true === $fix ) { + if ( isset( $this->tokens[ $text_domain_ptr ]['orig_content'] ) ) { + $replacement = $this->tokens[ $text_domain_ptr ]['orig_content']; + } else { + $replacement = $this->tokens[ $text_domain_ptr ]['content']; + } + + $replacement = str_replace( $text_domain_found, $this->new_text_domain, $replacement ); + + $this->phpcsFile->fixer->replaceToken( $text_domain_ptr, $replacement ); + } + } + } else { + $last_header_ptr = $comment_details['last_header_ptr']; + $last_header_matches = $comment_details['last_header_matches']; + + $fix = $this->phpcsFile->addFixableError( + 'Missing "Text Domain" in %s header', + $last_header_ptr, + 'MissingTextDomainHeader', + array( $type ) + ); + + if ( true === $fix ) { + if ( isset( $this->tokens[ $last_header_ptr ]['orig_content'] ) ) { + $replacement = $this->tokens[ $last_header_ptr ]['orig_content']; + } else { + $replacement = $this->tokens[ $last_header_ptr ]['content']; + } + + $replacement = str_replace( $last_header_matches[1], 'Text Domain', $replacement ); + $replacement = str_replace( $last_header_matches[2], $this->new_text_domain, $replacement ); + + if ( \T_DOC_COMMENT_OPEN_TAG === $this->tokens[ $stackPtr ]['code'] ) { + for ( $i = ( $last_header_ptr - 1 ); ; $i-- ) { + if ( $this->tokens[ $i ]['line'] !== $this->tokens[ $last_header_ptr ]['line'] ) { + ++$i; + break; + } + } + + $replacement = $this->phpcsFile->eolChar + . GetTokensAsString::origContent( $this->phpcsFile, $i, ( $last_header_ptr - 1 ) ) + . $replacement; + } + + $this->phpcsFile->fixer->addContent( $comment_details['last_header_ptr'], $replacement ); + } + } + } + + return $skip_to; + } + + /** + * Examine an individual token in a larger comment for plugin/theme headers. + * + * @since 1.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $regex The regex to use to examine the comment line. + * @param array $headers Valid headers for a plugin or theme. + * @param array $comment_details The information collected so far. + * + * @return array Adjusted $comment_details array + */ + protected function examine_comment_line( $stackPtr, $regex, $headers, $comment_details ) { + if ( preg_match( $regex, $this->tokens[ $stackPtr ]['content'], $matches ) === 1 ) { + ++$comment_details['headers_found']; + + if ( true === $headers[ $matches[1] ] ) { + $comment_details['required_header_found'] = true; + } + + if ( 'Text Domain' === $matches[1] ) { + $comment_details['text_domain_ptr'] = $stackPtr; + $comment_details['text_domain_found'] = trim( $matches[2] ); + } + + $comment_details['last_header_ptr'] = $stackPtr; + $comment_details['last_header_matches'] = $matches; + } + + return $comment_details; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php new file mode 100644 index 00000000..afd98fd2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php @@ -0,0 +1,371 @@ + true, + 'php://output' => true, + 'php://stdin' => true, + 'php://stdout' => true, + 'php://stderr' => true, + ); + + /** + * Local input streams which should not be flagged for the file system function checks if + * the $filename starts with them. + * + * @link https://www.php.net/wrappers.php + * + * @since 2.1.0 + * @since 3.0.0 The visibility was changed from `protected` to `private`. + * + * @var array + */ + private $allowed_local_stream_partials = array( + 'php://temp/', + 'php://fd/', + ); + + /** + * Local input stream constants which should not be flagged for the file system function checks. + * + * @link https://www.php.net/wrappers.php + * + * @since 2.1.0 + * @since 3.0.0 The visibility was changed from `protected` to `private`. + * + * @var array + */ + private $allowed_local_stream_constants = array( + 'STDIN' => true, + 'STDOUT' => true, + 'STDERR' => true, + ); + + /** + * Groups of functions to restrict. + * + * Example: groups => array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'since' => '4.9.0', //=> the WP version in which the alternative became available. + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'curl' => array( + 'type' => 'warning', + 'message' => 'Using cURL functions is highly discouraged. Use wp_remote_get() instead.', + 'since' => '2.7.0', + 'functions' => array( + 'curl_*', + ), + 'allow' => array( + 'curl_version' => true, + ), + ), + + 'parse_url' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.', + 'since' => '4.4.0', + 'functions' => array( + 'parse_url', + ), + ), + + 'json_encode' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use wp_json_encode() instead.', + 'since' => '4.1.0', + 'functions' => array( + 'json_encode', + ), + ), + + 'file_get_contents' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use wp_remote_get() for remote URLs instead.', + 'since' => '2.7.0', + 'functions' => array( + 'file_get_contents', + ), + ), + + 'unlink' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use wp_delete_file() to delete a file.', + 'since' => '4.2.0', + 'functions' => array( + 'unlink', + ), + ), + + 'rename' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use WP_Filesystem::move() to rename a file.', + 'since' => '2.5.0', + 'functions' => array( + 'rename', + ), + ), + + 'file_system_operations' => array( + 'type' => 'warning', + 'message' => 'File operations should use WP_Filesystem methods instead of direct PHP filesystem calls. Found: %s().', + 'since' => '2.5.0', + 'functions' => array( + 'chgrp', + 'chmod', + 'chown', + 'fclose', + 'file_put_contents', + 'fopen', + 'fputs', + 'fread', + 'fsockopen', + 'fwrite', + 'is_writable', + 'is_writeable', + 'mkdir', + 'pfsockopen', + 'readfile', + 'rmdir', + 'touch', + ), + ), + + 'strip_tags' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use the more comprehensive wp_strip_all_tags() instead.', + 'since' => '2.9.0', + 'functions' => array( + 'strip_tags', + ), + ), + + 'rand_seeding' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Rand seeding is not necessary when using the wp_rand() function (as you should).', + 'since' => '2.6.2', + 'functions' => array( + 'mt_srand', + 'srand', + ), + ), + + 'rand' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use the far less predictable wp_rand() instead.', + 'since' => '2.6.2', + 'functions' => array( + 'mt_rand', + 'rand', + ), + ), + ); + } + + /** + * Process a matched token. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $this->set_minimum_wp_version(); + + /* + * Deal with exceptions. + */ + switch ( $matched_content ) { + case 'strip_tags': + /* + * The function `wp_strip_all_tags()` is only a valid alternative when + * only the first parameter, `$string`, is passed to `strip_tags()`. + */ + $has_allowed_tags = PassedParameters::getParameter( $this->phpcsFile, $stackPtr, 2, 'allowed_tags' ); + if ( false !== $has_allowed_tags ) { + return; + } + + unset( $has_allowed_tags ); + break; + + case 'parse_url': + /* + * Before WP 4.7.0, the function `wp_parse_url()` was only a valid alternative + * if the second param - `$component` - was not passed to `parse_url()`. + * + * @see https://developer.wordpress.org/reference/functions/wp_parse_url/#changelog + */ + $has_component = PassedParameters::getParameter( $this->phpcsFile, $stackPtr, 2, 'component' ); + if ( false !== $has_component + && $this->wp_version_compare( $this->minimum_wp_version, '4.7.0', '<' ) + ) { + return; + } + + unset( $has_component ); + break; + + case 'file_get_contents': + /* + * Using `wp_remote_get()` will only work for remote URLs. + * See if we can determine is this function call is for a local file and if so, bow out. + */ + $params = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + + $use_include_path_param = PassedParameters::getParameterFromStack( $params, 2, 'use_include_path' ); + if ( false !== $use_include_path_param && 'true' === $use_include_path_param['clean'] ) { + // Setting `$use_include_path` to `true` is only relevant for local files. + return; + } + + $filename_param = PassedParameters::getParameterFromStack( $params, 1, 'filename' ); + if ( false === $filename_param ) { + // If the file to get is not set, this is a non-issue anyway. + return; + } + + if ( strpos( $filename_param['clean'], 'http:' ) !== false + || strpos( $filename_param['clean'], 'https:' ) !== false + ) { + // Definitely a URL, throw notice. + break; + } + + $contains_wp_path_constant = preg_match( + '`\b(?:ABSPATH|WP_(?:CONTENT|PLUGIN)_DIR|WPMU_PLUGIN_DIR|TEMPLATEPATH|STYLESHEETPATH|(?:MU)?PLUGINDIR)\b`', + $filename_param['clean'] + ); + if ( 1 === $contains_wp_path_constant ) { + // Using any of the constants matched in this regex is an indicator of a local file. + return; + } + + $contains_wp_path_function_call = preg_match( + '`(?:get_home_path|plugin_dir_path|get_(?:stylesheet|template)_directory|wp_upload_dir)\s*\(`i', + $filename_param['clean'] + ); + if ( 1 === $contains_wp_path_function_call ) { + // Using any of the functions matched in the regex is an indicator of a local file. + return; + } + + if ( $this->is_local_data_stream( $filename_param['clean'] ) === true ) { + // Local data stream. + return; + } + + unset( $params, $use_include_path_param, $filename_param, $contains_wp_path_constant, $contains_wp_path_function_call ); + break; + + case 'file_put_contents': + case 'fopen': + case 'readfile': + /* + * Allow for handling raw data streams from the request body. + * + * Note: at this time (December 2022) these three functions use the same parameter name for their + * first parameter. If this would change at any point in the future, this code will need to + * be made more modular and will need to pass the parameter name based on the function call detected. + */ + $filename_param = PassedParameters::getParameter( $this->phpcsFile, $stackPtr, 1, 'filename' ); + if ( false === $filename_param ) { + // If the file to work with is not set, local data streams don't come into play. + break; + } + + if ( $this->is_local_data_stream( $filename_param['clean'] ) === true ) { + // Local data stream. + return; + } + + unset( $filename_param ); + break; + } + + if ( ! isset( $this->groups[ $group_name ]['since'] ) ) { + return parent::process_matched_token( $stackPtr, $group_name, $matched_content ); + } + + // Verify if the alternative is available in the minimum supported WP version. + if ( $this->wp_version_compare( $this->groups[ $group_name ]['since'], $this->minimum_wp_version, '<=' ) ) { + return parent::process_matched_token( $stackPtr, $group_name, $matched_content ); + } + } + + /** + * Determine based on the "clean" parameter value, whether a file parameter points to + * a local data stream. + * + * @param string $clean_param_value Parameter value without comments. + * + * @return bool True if this is a local data stream. False otherwise. + */ + protected function is_local_data_stream( $clean_param_value ) { + + $stripped = TextStrings::stripQuotes( $clean_param_value ); + if ( isset( $this->allowed_local_streams[ $stripped ] ) + || isset( $this->allowed_local_stream_constants[ $clean_param_value ] ) + ) { + return true; + } + + foreach ( $this->allowed_local_stream_partials as $partial ) { + if ( strpos( $stripped, $partial ) === 0 ) { + return true; + } + } + + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapabilitiesSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapabilitiesSniff.php new file mode 100644 index 00000000..3a86562d --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapabilitiesSniff.php @@ -0,0 +1,478 @@ + The key is the name of a function we're targetting, + * the value is an array containing the 1-based parameter position + * of the "capability" parameter within the function, as well as + * the name of the parameter as declared in the function. + * If the parameter name has been renamed since the release of PHP 8.0, + * the parameter can be set as an array. + */ + protected $target_functions = array( + 'add_comments_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_dashboard_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_links_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_management_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_media_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_menu_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_object_page' => array( // Deprecated since WP 4.5.0. + 'position' => 3, + 'name' => 'capability', + ), + 'add_options_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_pages_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_plugins_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_posts_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_submenu_page' => array( + 'position' => 4, + 'name' => 'capability', + ), + 'add_theme_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_users_page' => array( + 'position' => 3, + 'name' => 'capability', + ), + 'add_utility_page' => array( // Deprecated since WP 4.5.0. + 'position' => 3, + 'name' => 'capability', + ), + 'author_can' => array( + 'position' => 2, + 'name' => 'capability', + ), + 'current_user_can' => array( + 'position' => 1, + 'name' => 'capability', + ), + 'current_user_can_for_blog' => array( + 'position' => 2, + 'name' => 'capability', + ), + 'map_meta_cap' => array( + 'position' => 1, + 'name' => 'cap', + ), + 'user_can' => array( + 'position' => 2, + 'name' => 'capability', + ), + ); + + /** + * List of core roles which should not to be used directly. + * + * @since 3.0.0 + * + * @var array Key is role available in WP Core, value irrelevant. + */ + private $core_roles = array( + 'super_admin' => true, + 'administrator' => true, + 'editor' => true, + 'author' => true, + 'contributor' => true, + 'subscriber' => true, + ); + + /** + * List of known primitive and meta core capabilities. + * + * Sources: + * - {@link https://wordpress.org/support/article/roles-and-capabilities/ Roles and Capabilities handbook page} + * - The `map_meta_cap()` function in the `src/wp-includes/capabilities.php` file. + * - The tests in the `tests/phpunit/tests/user/capabilities.php` file. + * + * List is sorted alphabetically. + * + * {@internal To be updated after every major release. Last updated for WordPress 6.1.0.} + * + * @since 3.0.0 + * + * @var array All capabilities available in core. + */ + private $core_capabilities = array( + 'activate_plugin' => true, + 'activate_plugins' => true, + 'add_comment_meta' => true, + 'add_post_meta' => true, + 'add_term_meta' => true, + 'add_user_meta' => true, + 'add_users' => true, + 'assign_categories' => true, + 'assign_post_tags' => true, + 'assign_term' => true, + 'create_app_password' => true, + 'create_sites' => true, + 'create_users' => true, + 'customize' => true, + 'deactivate_plugin' => true, + 'deactivate_plugins' => true, + 'delete_app_password' => true, + 'delete_app_passwords' => true, + 'delete_block' => true, // Only seen in tests. + 'delete_blocks' => true, // Alias for 'delete_posts', but supported. + 'delete_categories' => true, + 'delete_comment_meta' => true, + 'delete_others_blocks' => true, // Alias for 'delete_others_posts', but supported. + 'delete_others_pages' => true, + 'delete_others_posts' => true, + 'delete_page' => true, // Alias, but supported. + 'delete_pages' => true, + 'delete_plugins' => true, + 'delete_post_tags' => true, + 'delete_post' => true, // Alias, but supported. + 'delete_post_meta' => true, + 'delete_posts' => true, + 'delete_private_blocks' => true, // Alias for 'delete_private_posts', but supported. + 'delete_private_pages' => true, + 'delete_private_posts' => true, + 'delete_published_blocks' => true, // Alias for 'delete_published_posts', but supported. + 'delete_published_pages' => true, + 'delete_published_posts' => true, + 'delete_site' => true, + 'delete_sites' => true, + 'delete_term' => true, + 'delete_term_meta' => true, + 'delete_themes' => true, + 'delete_user' => true, // Alias for 'delete_users', but supported. + 'delete_user_meta' => true, + 'delete_users' => true, + 'edit_app_password' => true, + 'edit_categories' => true, + 'edit_block' => true, // Only seen in tests. + 'edit_blocks' => true, // Alias for 'edit_posts', but supported. + 'edit_comment' => true, // Alias, but supported. + 'edit_comment_meta' => true, + 'edit_css' => true, + 'edit_dashboard' => true, + 'edit_files' => true, + 'edit_others_blocks' => true, // Alias for 'edit_others_posts', but supported. + 'edit_others_pages' => true, + 'edit_others_posts' => true, + 'edit_page' => true, // Alias, but supported. + 'edit_pages' => true, + 'edit_plugins' => true, + 'edit_post_tags' => true, + 'edit_post' => true, // Alias, but supported. + 'edit_post_meta' => true, + 'edit_posts' => true, + 'edit_private_blocks' => true, // Alias for 'edit_private_posts', but supported. + 'edit_private_pages' => true, + 'edit_private_posts' => true, + 'edit_published_blocks' => true, // Alias for 'edit_published_posts', but supported. + 'edit_published_pages' => true, + 'edit_published_posts' => true, + 'edit_term' => true, + 'edit_term_meta' => true, + 'edit_theme_options' => true, + 'edit_themes' => true, + 'edit_user' => true, // Alias for 'edit_users', but supported. + 'edit_user_meta' => true, + 'edit_users' => true, + 'erase_others_personal_data' => true, + 'export' => true, + 'export_others_personal_data' => true, + 'import' => true, + 'install_languages' => true, + 'install_plugins' => true, + 'install_themes' => true, + 'list_app_passwords' => true, + 'list_users' => true, + 'manage_categories' => true, + 'manage_links' => true, + 'manage_network' => true, + 'manage_network_options' => true, + 'manage_network_plugins' => true, + 'manage_network_themes' => true, + 'manage_network_users' => true, + 'manage_options' => true, + 'manage_post_tags' => true, + 'manage_privacy_options' => true, + 'manage_sites' => true, + 'moderate_comments' => true, + 'publish_blocks' => true, // Alias for 'publish_posts', but supported. + 'publish_pages' => true, + 'publish_post' => true, // Alias, but supported. + 'publish_posts' => true, + 'promote_user' => true, + 'promote_users' => true, + 'read' => true, + 'read_block' => true, // Only seen in tests. + 'read_post' => true, // Alias, but supported. + 'read_page' => true, // Alias, but supported. + 'read_app_password' => true, + 'read_private_blocks' => true, // Alias for 'read_private_posts', but supported. + 'read_private_pages' => true, + 'read_private_posts' => true, + 'remove_user' => true, // Alias for 'remove_users', but supported. + 'remove_users' => true, + 'resume_plugin' => true, // Alias for 'resume_plugins', but supported. + 'resume_plugins' => true, + 'resume_theme' => true, // Alias for 'resume_themes', but supported. + 'resume_themes' => true, + 'setup_network' => true, + 'switch_themes' => true, + 'unfiltered_html' => true, + 'unfiltered_upload' => true, + 'update_core' => true, + 'update_https' => true, + 'update_languages' => true, + 'update_plugins' => true, + 'update_php' => true, + 'update_themes' => true, + 'upgrade_network' => true, + 'upload_files' => true, + 'upload_plugins' => true, + 'upload_themes' => true, + 'view_site_health_checks' => true, + ); + + /** + * List of deprecated core capabilities. + * + * User Levels were deprecated in version 3.0. + * + * {@internal To be updated after every major release. Last updated for WordPress 6.1.0.} + * + * @link https://github.com/WordPress/wordpress-develop/blob/master/tests/phpunit/tests/user/capabilities.php + * + * @since 3.0.0 + * + * @var array All deprecated capabilities in core. + */ + private $deprecated_capabilities = array( + 'level_10' => '3.0.0', + 'level_9' => '3.0.0', + 'level_8' => '3.0.0', + 'level_7' => '3.0.0', + 'level_6' => '3.0.0', + 'level_5' => '3.0.0', + 'level_4' => '3.0.0', + 'level_3' => '3.0.0', + 'level_2' => '3.0.0', + 'level_1' => '3.0.0', + 'level_0' => '3.0.0', + ); + + /** + * Process the parameters of a matched function. + * + * @since 3.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $function_details = $this->target_functions[ $matched_content ]; + + $parameter = PassedParameters::getParameterFromStack( + $parameters, + $function_details['position'], + $function_details['name'] + ); + + if ( false === $parameter ) { + return; + } + + // If the parameter is anything other than T_CONSTANT_ENCAPSED_STRING throw a warning and bow out. + $first_non_empty = null; + for ( $i = $parameter['start']; $i <= $parameter['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + if ( \T_CONSTANT_ENCAPSED_STRING !== $this->tokens[ $i ]['code'] + || null !== $first_non_empty + ) { + // Throw warning at low severity. + $this->phpcsFile->addWarning( + 'Couldn\'t determine the value passed to the $%s parameter in function call to %s(). Please check if it matches a valid capability. Found: %s', + $i, + 'Undetermined', + array( + $function_details['name'], + $matched_content, + $parameter['clean'], + ), + 3 // Message severity set to below default. + ); + return; + } + + $first_non_empty = $i; + } + + if ( null === $first_non_empty ) { + // Parse error. Bow out. + return; + } + + /* + * As of this point we know that the `$capabilities` parameter only contains the one token + * and that that token is a `T_CONSTANT_ENCAPSED_STRING`. + */ + $matched_parameter = TextStrings::stripQuotes( $this->tokens[ $first_non_empty ]['content'] ); + + if ( isset( $this->core_capabilities[ $matched_parameter ] ) ) { + return; + } + + if ( empty( $matched_parameter ) ) { + $this->phpcsFile->addError( + 'An empty string is not a valid capability. Empty string found as the $%s parameter in a function call to %s()"', + $first_non_empty, + 'Invalid', + array( + $function_details['name'], + $matched_content, + ) + ); + return; + } + + // Check if additional capabilities were registered via the ruleset and if the found capability matches any of those. + $custom_capabilities = RulesetPropertyHelper::merge_custom_array( $this->custom_capabilities, array() ); + if ( isset( $custom_capabilities[ $matched_parameter ] ) ) { + return; + } + + if ( isset( $this->deprecated_capabilities[ $matched_parameter ] ) ) { + $this->set_minimum_wp_version(); + $is_error = $this->wp_version_compare( $this->deprecated_capabilities[ $matched_parameter ], $this->minimum_wp_version, '<' ); + + $data = array( + $matched_parameter, + $matched_content, + $this->deprecated_capabilities[ $matched_parameter ], + ); + + MessageHelper::addMessage( + $this->phpcsFile, + 'The capability "%s", found in the function call to %s(), has been deprecated since WordPress version %s.', + $first_non_empty, + $is_error, + 'Deprecated', + $data + ); + return; + } + + if ( isset( $this->core_roles[ $matched_parameter ] ) ) { + $this->phpcsFile->addError( + 'Capabilities should be used instead of roles. Found "%s" in function call to %s()', + $first_non_empty, + 'RoleFound', + array( + $matched_parameter, + $matched_content, + ) + ); + return; + } + + $this->phpcsFile->addWarning( + 'Found unknown capability "%s" in function call to %s(). Please check the spelling of the capability. If this is a custom capability, please verify the capability is registered with WordPress via a call to WP_Role(s)->add_cap().' . \PHP_EOL . 'Custom capabilities can be made known to this sniff by setting the "custom_capabilities" property in the PHPCS ruleset.', + $first_non_empty, + 'Unknown', + array( + $matched_parameter, + $matched_content, + ) + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapitalPDangitSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapitalPDangitSniff.php new file mode 100644 index 00000000..c183f156 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CapitalPDangitSniff.php @@ -0,0 +1,315 @@ +\'"()]*?\.(?:php|js|css|png|j[e]?pg|gif|pot))#i'; + + /** + * Regex to match a large number or spelling variations of WordPress in class names. + * + * @var string + */ + const WP_CLASSNAME_REGEX = '`(?:^|_)(Word[_]*Pres+)(?:_|$)`i'; + + /** + * Comment tokens we want to listen for as they contain text strings. + * + * @var array + */ + private $comment_text_tokens = array( + \T_DOC_COMMENT => \T_DOC_COMMENT, + \T_DOC_COMMENT_STRING => \T_DOC_COMMENT_STRING, + \T_COMMENT => \T_COMMENT, + ); + + /** + * Combined text string and comment tokens array. + * + * This property is set in the register() method and used for lookups. + * + * @var array + */ + private $text_and_comment_tokens = array(); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.12.0 + * + * @return array + */ + public function register() { + // Union the arrays - keeps the array keys. + $this->text_and_comment_tokens = ( Tokens::$textStringTokens + $this->comment_text_tokens ); + + $targets = $this->text_and_comment_tokens; + $targets += Tokens::$ooScopeTokens; + $targets[ \T_NAMESPACE ] = \T_NAMESPACE; + + // Also sniff for array tokens to make skipping anything within those more efficient. + $targets += Collections::arrayOpenTokensBC(); + $targets += Collections::listTokens(); + $targets[ \T_OPEN_SQUARE_BRACKET ] = \T_OPEN_SQUARE_BRACKET; + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + /* + * Ignore tokens within array and list definitions as well as within + * array keys as this is a false positive in 80% of all cases. + * + * The return values skip to the end of the array. + * This prevents the sniff "hanging" on very long configuration arrays. + */ + if ( ( \T_ARRAY === $this->tokens[ $stackPtr ]['code'] + || \T_LIST === $this->tokens[ $stackPtr ]['code'] ) + && isset( $this->tokens[ $stackPtr ]['parenthesis_closer'] ) + ) { + return $this->tokens[ $stackPtr ]['parenthesis_closer']; + } + + if ( ( \T_OPEN_SHORT_ARRAY === $this->tokens[ $stackPtr ]['code'] + || \T_OPEN_SQUARE_BRACKET === $this->tokens[ $stackPtr ]['code'] ) + && isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) + ) { + return $this->tokens[ $stackPtr ]['bracket_closer']; + } + + /* + * Deal with misspellings in namespace names. + * These are not auto-fixable, but need the attention of a developer. + */ + if ( \T_NAMESPACE === $this->tokens[ $stackPtr ]['code'] ) { + $ns_name = Namespaces::getDeclaredName( $this->phpcsFile, $stackPtr ); + if ( empty( $ns_name ) ) { + // Namespace operator or declaration without name. + return; + } + + $levels = explode( '\\', $ns_name ); + foreach ( $levels as $level ) { + if ( preg_match_all( self::WP_CLASSNAME_REGEX, $level, $matches, \PREG_PATTERN_ORDER ) > 0 ) { + $misspelled = $this->retrieve_misspellings( $matches[1] ); + + if ( ! empty( $misspelled ) ) { + $this->phpcsFile->addWarning( + 'Please spell "WordPress" correctly. Found: "%s" as part of the namespace name.', + $stackPtr, + 'MisspelledNamespaceName', + array( implode( ', ', $misspelled ) ) + ); + } + } + } + + return; + } + + /* + * Deal with misspellings in class/interface/trait/enum names. + * These are not auto-fixable, but need the attention of a developer. + */ + if ( isset( Tokens::$ooScopeTokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $classname = ObjectDeclarations::getName( $this->phpcsFile, $stackPtr ); + if ( empty( $classname ) ) { + return; + } + + if ( preg_match_all( self::WP_CLASSNAME_REGEX, $classname, $matches, \PREG_PATTERN_ORDER ) > 0 ) { + $misspelled = $this->retrieve_misspellings( $matches[1] ); + + if ( ! empty( $misspelled ) ) { + $this->phpcsFile->addWarning( + 'Please spell "WordPress" correctly. Found: "%s" as part of the class/interface/trait/enum name.', + $stackPtr, + 'MisspelledClassName', + array( implode( ', ', $misspelled ) ) + ); + } + } + + return; + } + + /* + * Deal with misspellings in text strings and documentation. + */ + + // Ignore content of docblock @link tags. + if ( \T_DOC_COMMENT_STRING === $this->tokens[ $stackPtr ]['code'] + || \T_DOC_COMMENT === $this->tokens[ $stackPtr ]['code'] + ) { + + $comment_tag = $this->phpcsFile->findPrevious( + array( \T_DOC_COMMENT_TAG, \T_DOC_COMMENT_OPEN_TAG ), + ( $stackPtr - 1 ) + ); + if ( false !== $comment_tag + && \T_DOC_COMMENT_TAG === $this->tokens[ $comment_tag ]['code'] + && '@link' === $this->tokens[ $comment_tag ]['content'] + ) { + // @link tag, so ignore. + return; + } + } + + // Ignore constant declarations via define(). + if ( ContextHelper::is_in_function_call( $this->phpcsFile, $stackPtr, array( 'define' => true ), true, true ) ) { + return; + } + + // Ignore constant declarations using the const keyword. + $stop_points = array( + \T_CONST, + \T_SEMICOLON, + \T_OPEN_TAG, + \T_CLOSE_TAG, + \T_OPEN_CURLY_BRACKET, + ); + $maybe_const = $this->phpcsFile->findPrevious( $stop_points, ( $stackPtr - 1 ) ); + if ( false !== $maybe_const && \T_CONST === $this->tokens[ $maybe_const ]['code'] ) { + return; + } + + $content = $this->tokens[ $stackPtr ]['content']; + + if ( preg_match_all( self::WP_REGEX, $content, $matches, ( \PREG_PATTERN_ORDER | \PREG_OFFSET_CAPTURE ) ) > 0 ) { + /* + * Prevent some typical false positives. + */ + if ( isset( $this->text_and_comment_tokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $offset = 0; + foreach ( $matches[1] as $key => $match_data ) { + $next_offset = ( $match_data[1] + \strlen( $match_data[0] ) ); + + // Prevent matches on part of a URL. + if ( preg_match( '`http[s]?://[^\s<>\'"()]*' . preg_quote( $match_data[0], '`' ) . '`', $content, $discard, 0, $offset ) === 1 ) { + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`[a-z]+=(["\'])' . preg_quote( $match_data[0], '`' ) . '\1`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on html attributes like: `value="wordpress"`. + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`\\\\\'' . preg_quote( $match_data[0], '`' ) . '\\\\\'`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on xpath queries and such: `\'wordpress\'`. + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`(?:\?|&|&)[a-z0-9_]+=' . preg_quote( $match_data[0], '`' ) . '(?:&|$)`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on url query strings: `?something=wordpress`. + unset( $matches[1][ $key ] ); + } + + $offset = $next_offset; + } + + if ( empty( $matches[1] ) ) { + return; + } + } + + $misspelled = $this->retrieve_misspellings( $matches[1] ); + + if ( empty( $misspelled ) ) { + return; + } + + $code = 'MisspelledInText'; + if ( isset( Tokens::$commentTokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $code = 'MisspelledInComment'; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Please spell "WordPress" correctly. Found %s misspelling(s): %s', + $stackPtr, + $code, + array( + \count( $misspelled ), + implode( ', ', $misspelled ), + ) + ); + + if ( true === $fix ) { + // Apply fixes based on offset to ensure we don't replace false positives. + $replacement = $content; + foreach ( $matches[1] as $match ) { + $replacement = substr_replace( $replacement, 'WordPress', $match[1], \strlen( $match[0] ) ); + } + + $this->phpcsFile->fixer->replaceToken( $stackPtr, $replacement ); + } + } + } + + /** + * Retrieve a list of misspellings based on an array of matched variations on the target word. + * + * @param array $match_stack Array of matched variations of the target word. + * @return array Array containing only the misspelled variants. + */ + protected function retrieve_misspellings( $match_stack ) { + $misspelled = array(); + foreach ( $match_stack as $match ) { + // Deal with multi-dimensional arrays when capturing offset. + if ( \is_array( $match ) ) { + $match = $match[0]; + } + + if ( 'WordPress' !== $match ) { + $misspelled[] = $match; + } + } + + return $misspelled; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/ClassNameCaseSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/ClassNameCaseSniff.php new file mode 100644 index 00000000..e1abd225 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/ClassNameCaseSniff.php @@ -0,0 +1,835 @@ +class_groups as $name ) { + $name_lc = $name . '_lc'; + $this->$name_lc = array_map( 'strtolower', $this->$name ); + $this->$name = array_combine( $this->$name_lc, $this->$name ); + } + } + + /** + * Groups of classes to restrict. + * + * @since 3.0.0 + * + * @return array + */ + public function getGroups() { + $groups = array(); + foreach ( $this->class_groups as $name ) { + $name_lc = $name . '_lc'; + $groups[ $name ] = array( + 'classes' => $this->$name_lc, + ); + } + + return $groups; + } + + /** + * Process a matched token. + * + * @since 3.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. Will + * always be 'wp_classes'. + * @param string $matched_content The token content (class name) which was matched. + * in its original case. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $matched_unqualified = ltrim( $matched_content, '\\' ); + $matched_lowercase = strtolower( $matched_unqualified ); + $matched_proper_case = $this->get_proper_case( $matched_lowercase ); + + if ( $matched_unqualified === $matched_proper_case ) { + // Already using proper case, nothing to do. + return; + } + + $warning = 'It is strongly recommended to refer to classes by their properly cased name. Expected: %s Found: %s'; + $data = array( + $matched_proper_case, + $matched_unqualified, + ); + + $this->phpcsFile->addWarning( $warning, $stackPtr, 'Incorrect', $data ); + } + + /** + * Match a lowercase class name to its proper cased name. + * + * @since 3.0.0 + * + * @param string $matched_lc Lowercase class name. + * + * @return string + */ + private function get_proper_case( $matched_lc ) { + foreach ( $this->class_groups as $name ) { + $current = $this->$name; // Needed to prevent issues with PHP < 7.0. + if ( isset( $current[ $matched_lc ] ) ) { + return $current[ $matched_lc ]; + } + } + + // Shouldn't be possible. + return ''; // @codeCoverageIgnore + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CronIntervalSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CronIntervalSniff.php new file mode 100644 index 00000000..fd48e1a2 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/CronIntervalSniff.php @@ -0,0 +1,321 @@ + 60, + 'HOUR_IN_SECONDS' => 3600, + 'DAY_IN_SECONDS' => 86400, + 'WEEK_IN_SECONDS' => 604800, + 'MONTH_IN_SECONDS' => 2592000, + 'YEAR_IN_SECONDS' => 31536000, + ); + + /** + * Function within which the hook should be found. + * + * @var array + */ + protected $valid_functions = array( + 'add_filter' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return Tokens::$stringTokens; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + $token = $this->tokens[ $stackPtr ]; + + if ( 'cron_schedules' !== TextStrings::stripQuotes( $token['content'] ) ) { + return; + } + + // Check if the text was found within a function call to add_filter(). + $functionPtr = ContextHelper::is_in_function_call( $this->phpcsFile, $stackPtr, $this->valid_functions ); + if ( false === $functionPtr ) { + return; + } + + $callback = PassedParameters::getParameter( $this->phpcsFile, $functionPtr, 2, 'callback' ); + if ( false === $callback ) { + return; + } + + if ( $stackPtr >= $callback['start'] && $stackPtr <= $callback['end'] ) { + // "cron_schedules" found in the second parameter, not the first. + return; + } + + // Detect callback function name. + $callbackArrayPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $callback['start'], ( $callback['end'] + 1 ), true ); + + // If callback is array, get second element. + if ( false !== $callbackArrayPtr + && ( \T_ARRAY === $this->tokens[ $callbackArrayPtr ]['code'] + || ( isset( Collections::shortArrayListOpenTokensBC()[ $this->tokens[ $callbackArrayPtr ]['code'] ] ) + && Arrays::isShortArray( $this->phpcsFile, $callbackArrayPtr ) === true ) + ) + ) { + $callback = PassedParameters::getParameter( $this->phpcsFile, $callbackArrayPtr, 2 ); + + if ( false === $callback ) { + $this->confused( $stackPtr ); + return; + } + } + + unset( $functionPtr ); + + // Search for the function in tokens. + $search = Tokens::$stringTokens; + $search[ \T_CLOSURE ] = \T_CLOSURE; + $search[ \T_FN ] = \T_FN; + $search[ \T_ELLIPSIS ] = \T_ELLIPSIS; + $callbackFunctionPtr = $this->phpcsFile->findNext( $search, $callback['start'], ( $callback['end'] + 1 ) ); + + if ( false === $callbackFunctionPtr ) { + $this->confused( $stackPtr ); + return; + } + + if ( \T_CLOSURE === $this->tokens[ $callbackFunctionPtr ]['code'] + || \T_FN === $this->tokens[ $callbackFunctionPtr ]['code'] + ) { + $functionPtr = $callbackFunctionPtr; + } elseif ( \T_ELLIPSIS === $this->tokens[ $callbackFunctionPtr ]['code'] ) { + // Check if this is a PHP 8.1 first class callable. + $before = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $callbackFunctionPtr - 1 ), null, true ); + $after = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $callbackFunctionPtr + 1 ), null, true ); + if ( ( false !== $before && \T_OPEN_PARENTHESIS === $this->tokens[ $before ]['code'] ) + && ( false !== $after && \T_CLOSE_PARENTHESIS === $this->tokens[ $after ]['code'] ) + ) { + // Ok, now see if we can find the function name. + $beforeOpen = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $before - 1 ), null, true ); + if ( false !== $beforeOpen && \T_STRING === $this->tokens[ $beforeOpen ]['code'] ) { + $found_function = $this->find_function_by_name( $this->tokens[ $beforeOpen ]['content'] ); + if ( false !== $found_function ) { + $functionPtr = $found_function; + } + } + } + unset( $before, $after, $beforeOpen ); + } else { + $functionName = TextStrings::stripQuotes( $this->tokens[ $callbackFunctionPtr ]['content'] ); + $found_function = $this->find_function_by_name( $functionName ); + if ( false !== $found_function ) { + $functionPtr = $found_function; + } + } + + if ( ! isset( $functionPtr ) ) { + $this->confused( $stackPtr ); + return; + } + + if ( ! isset( $this->tokens[ $functionPtr ]['scope_opener'], $this->tokens[ $functionPtr ]['scope_closer'] ) ) { + return; + } + + $opening = $this->tokens[ $functionPtr ]['scope_opener']; + $closing = $this->tokens[ $functionPtr ]['scope_closer']; + for ( $i = $opening; $i <= $closing; $i++ ) { + + if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + if ( 'interval' === TextStrings::stripQuotes( $this->tokens[ $i ]['content'] ) ) { + $operator = $this->phpcsFile->findNext( \T_DOUBLE_ARROW, $i, null, false, null, true ); + if ( false === $operator ) { + $this->confused( $stackPtr ); + return; + } + + $valueStart = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $operator + 1 ), null, true, null, true ); + $valueEnd = $this->phpcsFile->findNext( array( \T_COMMA, \T_CLOSE_PARENTHESIS ), ( $valueStart + 1 ) ); + $value = ''; + $parentheses_count = 0; + for ( $j = $valueStart; $j <= $valueEnd; $j++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $j ]['code'] ] ) ) { + continue; + } + + if ( \T_NS_SEPARATOR === $this->tokens[ $j ]['code'] ) { + $value .= ' '; + continue; + } + + if ( $j === $valueEnd && \T_COMMA === $this->tokens[ $j ]['code'] ) { + break; + } + + // Make sure that PHP 7.4 numeric literals and PHP 8.1 explicit octals don't cause problems. + if ( \T_LNUMBER === $this->tokens[ $j ]['code'] + || \T_DNUMBER === $this->tokens[ $j ]['code'] + ) { + $number_info = Numbers::getCompleteNumber( $this->phpcsFile, $j ); + $value .= $number_info['decimal']; + $j = $number_info['last_token']; + continue; + } + + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $j ]['code'] ) { + $value .= $this->tokens[ $j ]['content']; + ++$parentheses_count; + continue; + } + + if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $j ]['code'] ) { + // Only add a close parenthesis if there are open parentheses. + if ( $parentheses_count > 0 ) { + $value .= $this->tokens[ $j ]['content']; + --$parentheses_count; + } + continue; + } + + $value .= $this->tokens[ $j ]['content']; + } + + if ( $parentheses_count > 0 ) { + // Make sure all open parenthesis are closed. + $value .= str_repeat( ')', $parentheses_count ); + } + + if ( is_numeric( $value ) ) { + $interval = $value; + break; + } + + // Deal correctly with WP time constants. + $value = str_replace( array_keys( $this->wp_time_constants ), array_values( $this->wp_time_constants ), $value ); + + // If all parentheses, digits and operators, eval! + if ( preg_match( '#^[\s\d()+*/-]+$#', $value ) > 0 ) { + $interval = eval( "return ( $value );" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. + break; + } + + $this->confused( $stackPtr ); + return; + } + } + } + + $this->min_interval = (int) $this->min_interval; + + if ( isset( $interval ) && $interval < $this->min_interval ) { + $minutes = round( ( $this->min_interval / 60 ), 1 ); + $this->phpcsFile->addWarning( + 'Scheduling crons at %s sec ( less than %s minutes ) is discouraged.', + $stackPtr, + 'CronSchedulesInterval', + array( + $interval, + $minutes, + ) + ); + return; + } + } + + /** + * Find a declared function in a file based on the function name. + * + * @param string $functionName The name of the function to find. + * + * @return int|false Integer stack pointer to the function keyword token or + * false if not found. + */ + private function find_function_by_name( $functionName ) { + $functionPtr = false; + for ( $ptr = 0; $ptr < $this->phpcsFile->numTokens; $ptr++ ) { + if ( \T_FUNCTION === $this->tokens[ $ptr ]['code'] ) { + $foundName = FunctionDeclarations::getName( $this->phpcsFile, $ptr ); + if ( $foundName === $functionName ) { + $functionPtr = $ptr; + break; + } elseif ( isset( $this->tokens[ $ptr ]['scope_closer'] ) ) { + // Skip to the end of the function definition. + $ptr = $this->tokens[ $ptr ]['scope_closer']; + } + } + } + + return $functionPtr; + } + + /** + * Add warning about unclear cron schedule change. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function confused( $stackPtr ) { + $this->phpcsFile->addWarning( + 'Detected changing of cron_schedules, but could not detect the interval value.', + $stackPtr, + 'ChangeDetected' + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedClassesSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedClassesSniff.php new file mode 100644 index 00000000..4c370434 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedClassesSniff.php @@ -0,0 +1,142 @@ + value + * in a custom ruleset. + * + * @uses \WordPressCS\WordPress\Helpers\MinimumWPVersionTrait::$minimum_wp_version + */ +final class DeprecatedClassesSniff extends AbstractClassRestrictionsSniff { + + use MinimumWPVersionTrait; + + /** + * List of deprecated classes with alternative when available. + * + * To be updated after every major release. + * + * Version numbers should be fully qualified. + * + * Last update: July 2023 for WP 6.3 at https://github.com/WordPress/wordpress-develop/commit/6281ce432c50345a57768bf53854d9b65b6cdd52 + * + * @var array + */ + private $deprecated_classes = array( + + // WP 3.1.0. + 'WP_User_Search' => array( + 'alt' => 'WP_User_Query', + 'version' => '3.1.0', + ), + + // WP 3.7.0. + 'WP_HTTP_Fsockopen' => array( + 'alt' => 'WP_HTTP::request()', + 'version' => '3.7.0', + ), + + // WP 4.9.0. + 'WP_Customize_New_Menu_Section' => array( + 'version' => '4.9.0', + ), + 'WP_Customize_New_Menu_Control' => array( + 'version' => '4.9.0', + ), + + // WP 5.3.0. + 'WP_Privacy_Data_Export_Requests_Table' => array( + 'alt' => 'WP_Privacy_Data_Export_Requests_List_Table', + 'version' => '5.3.0', + ), + 'WP_Privacy_Data_Removal_Requests_Table' => array( + 'alt' => 'WP_Privacy_Data_Removal_Requests_List_Table', + 'version' => '5.3.0', + ), + 'Services_JSON' => array( + 'alt' => 'The PHP native JSON extension', + 'version' => '5.3.0', + ), + 'Services_JSON_Error' => array( + 'alt' => 'The PHP native JSON extension', + 'version' => '5.3.0', + ), + ); + + /** + * Groups of classes to restrict. + * + * @return array + */ + public function getGroups() { + // Make sure all array keys are lowercase. + $this->deprecated_classes = array_change_key_case( $this->deprecated_classes, \CASE_LOWER ); + + return array( + 'deprecated_classes' => array( + 'classes' => array_keys( $this->deprecated_classes ), + ), + ); + } + + /** + * Process a matched token. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. Will + * always be 'deprecated_classes'. + * @param string $matched_content The token content (class name) which was matched + * in its original case. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $this->set_minimum_wp_version(); + + $class_name = ltrim( strtolower( $matched_content ), '\\' ); + + $message = 'The %s class has been deprecated since WordPress version %s.'; + $data = array( + ltrim( $matched_content, '\\' ), + $this->deprecated_classes[ $class_name ]['version'], + ); + + if ( ! empty( $this->deprecated_classes[ $class_name ]['alt'] ) ) { + $message .= ' Use %s instead.'; + $data[] = $this->deprecated_classes[ $class_name ]['alt']; + } + + MessageHelper::addMessage( + $this->phpcsFile, + $message, + $stackPtr, + ( $this->wp_version_compare( $this->deprecated_classes[ $class_name ]['version'], $this->minimum_wp_version, '<' ) ), + MessageHelper::stringToErrorcode( $class_name . 'Found' ), + $data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php new file mode 100644 index 00000000..fc7af255 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php @@ -0,0 +1,1653 @@ + value + * in a custom ruleset. + * + * @uses \WordPressCS\WordPress\Helpers\MinimumWPVersionTrait::$minimum_wp_version + */ +final class DeprecatedFunctionsSniff extends AbstractFunctionRestrictionsSniff { + + use MinimumWPVersionTrait; + + /** + * List of deprecated functions with alternative when available. + * + * To be updated after every major release. + * Last updated for WordPress 6.3. + * + * Version numbers should be fully qualified. + * Replacement functions should have parentheses. + * + * To retrieve a function list for comparison, the following tool is available: + * https://github.com/JDGrimes/wp-deprecated-code-scanner + * + * @var array + */ + private $deprecated_functions = array( + // WP 0.71. + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + 'version' => '0.71', + ), + 'the_category_ID' => array( + 'alt' => 'get_the_category()', + 'version' => '0.71', + ), + + // WP 1.2.0. + 'permalink_link' => array( + 'alt' => 'the_permalink()', + 'version' => '1.2.0', + ), + + // WP 1.5.0. + 'start_wp' => array( + // Verified correct alternative. + 'alt' => 'the Loop', + 'version' => '1.5.0', + ), + + // WP 1.5.1. + 'get_postdata' => array( + 'alt' => 'get_post()', + 'version' => '1.5.1', + ), + + // WP 2.0.0. + 'create_user' => array( + 'alt' => 'wp_create_user()', + 'version' => '2.0.0', + ), + 'next_post' => array( + 'alt' => 'next_post_link()', + 'version' => '2.0.0', + ), + 'previous_post' => array( + 'alt' => 'previous_post_link()', + 'version' => '2.0.0', + ), + 'user_can_create_draft' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_create_post' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_delete_post' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_delete_post_comments' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_edit_post' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_edit_post_comments' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_edit_post_date' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_edit_user' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_set_post_date' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + + // WP 2.1.0. + 'dropdown_cats' => array( + 'alt' => 'wp_dropdown_categories()', + 'version' => '2.1.0', + ), + 'get_archives' => array( + 'alt' => 'wp_get_archives()', + 'version' => '2.1.0', + ), + 'get_author_link' => array( + 'alt' => 'get_author_posts_url()', + 'version' => '2.1.0', + ), + 'get_autotoggle' => array( + 'alt' => '', + 'version' => '2.1.0', + ), + 'get_link' => array( + 'alt' => 'get_bookmark()', + 'version' => '2.1.0', + ), + 'get_linkcatname' => array( + 'alt' => 'get_category()', + 'version' => '2.1.0', + ), + 'get_linkobjects' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_linkobjectsbyname' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_linkrating' => array( + 'alt' => 'sanitize_bookmark_field()', + 'version' => '2.1.0', + ), + 'get_links' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_links_list' => array( + 'alt' => 'wp_list_bookmarks()', + 'version' => '2.1.0', + ), + 'get_links_withrating' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_linksbyname' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_linksbyname_withrating' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', + ), + 'get_settings' => array( + 'alt' => 'get_option()', + 'version' => '2.1.0', + ), + 'link_pages' => array( + 'alt' => 'wp_link_pages()', + 'version' => '2.1.0', + ), + 'links_popup_script' => array( + 'alt' => '', + 'version' => '2.1.0', + ), + 'list_authors' => array( + 'alt' => 'wp_list_authors()', + 'version' => '2.1.0', + ), + 'list_cats' => array( + 'alt' => 'wp_list_categories()', + 'version' => '2.1.0', + ), + 'tinymce_include' => array( + 'alt' => 'wp_editor()', + 'version' => '2.1.0', + ), + 'wp_get_links' => array( + 'alt' => 'wp_list_bookmarks()', + 'version' => '2.1.0', + ), + 'wp_get_linksbyname' => array( + 'alt' => 'wp_list_bookmarks()', + 'version' => '2.1.0', + ), + 'wp_get_post_cats' => array( + 'alt' => 'wp_get_post_categories()', + 'version' => '2.1.0', + ), + 'wp_list_cats' => array( + 'alt' => 'wp_list_categories()', + 'version' => '2.1.0', + ), + 'wp_set_post_cats' => array( + 'alt' => 'wp_set_post_categories()', + 'version' => '2.1.0', + ), + + // WP 2.2.0. + 'comments_rss' => array( + 'alt' => 'get_post_comments_feed_link()', + 'version' => '2.2.0', + ), + + // WP 2.3.0. + 'permalink_single_rss' => array( + 'alt' => 'the_permalink_rss()', + 'version' => '2.3.0', + ), + + // WP 2.5.0. + 'comments_rss_link' => array( + 'alt' => 'post_comments_feed_link()', + 'version' => '2.5.0', + ), + 'documentation_link' => array( + 'alt' => '', + 'version' => '2.5.0', + ), + 'get_attachment_icon' => array( + 'alt' => 'wp_get_attachment_image()', + 'version' => '2.5.0', + ), + 'get_attachment_icon_src' => array( + 'alt' => 'wp_get_attachment_image_src()', + 'version' => '2.5.0', + ), + 'get_attachment_innerHTML' => array( + 'alt' => 'wp_get_attachment_image()', + 'version' => '2.5.0', + ), + 'get_author_rss_link' => array( + 'alt' => 'get_author_feed_link()', + 'version' => '2.5.0', + ), + 'get_category_rss_link' => array( + 'alt' => 'get_category_feed_link()', + 'version' => '2.5.0', + ), + 'get_the_attachment_link' => array( + 'alt' => 'wp_get_attachment_link()', + 'version' => '2.5.0', + ), + 'gzip_compression' => array( + 'alt' => '', + 'version' => '2.5.0', + ), + 'wp_clearcookie' => array( + 'alt' => 'wp_clear_auth_cookie()', + 'version' => '2.5.0', + ), + 'wp_get_cookie_login' => array( + 'alt' => '', + 'version' => '2.5.0', + ), + 'wp_login' => array( + 'alt' => 'wp_signon()', + 'version' => '2.5.0', + ), + 'wp_setcookie' => array( + 'alt' => 'wp_set_auth_cookie()', + 'version' => '2.5.0', + ), + + // WP 2.6.0. + 'dropdown_categories' => array( + 'alt' => 'wp_category_checklist()', + 'version' => '2.6.0', + ), + 'dropdown_link_categories' => array( + 'alt' => 'wp_link_category_checklist()', + 'version' => '2.6.0', + ), + + // WP 2.7.0. + 'get_commentdata' => array( + 'alt' => 'get_comment()', + 'version' => '2.7.0', + ), + // This is a method i.e. WP_Filesystem_Base::find_base_dir() See #731. + 'find_base_dir' => array( + 'alt' => 'WP_Filesystem::abspath()', + 'version' => '2.7.0', + ), + // This is a method i.e. WP_Filesystem_Base::get_base_dir() See #731. + 'get_base_dir' => array( + 'alt' => 'WP_Filesystem::abspath()', + 'version' => '2.7.0', + ), + + // WP 2.8.0. + '__ngettext' => array( + 'alt' => '_n()', + 'version' => '2.8.0', + ), + '__ngettext_noop' => array( + 'alt' => '_n_noop()', + 'version' => '2.8.0', + ), + 'attribute_escape' => array( + 'alt' => 'esc_attr()', + 'version' => '2.8.0', + ), + 'get_author_name' => array( + 'alt' => 'get_the_author_meta(\'display_name\')', + 'version' => '2.8.0', + ), + 'get_category_children' => array( + 'alt' => 'get_term_children()', + 'version' => '2.8.0', + ), + 'get_catname' => array( + 'alt' => 'get_cat_name()', + 'version' => '2.8.0', + ), + 'get_the_author_aim' => array( + 'alt' => 'get_the_author_meta(\'aim\')', + 'version' => '2.8.0', + ), + 'get_the_author_description' => array( + 'alt' => 'get_the_author_meta(\'description\')', + 'version' => '2.8.0', + ), + 'get_the_author_email' => array( + 'alt' => 'get_the_author_meta(\'email\')', + 'version' => '2.8.0', + ), + 'get_the_author_firstname' => array( + 'alt' => 'get_the_author_meta(\'first_name\')', + 'version' => '2.8.0', + ), + 'get_the_author_icq' => array( + 'alt' => 'get_the_author_meta(\'icq\')', + 'version' => '2.8.0', + ), + 'get_the_author_ID' => array( + 'alt' => 'get_the_author_meta(\'ID\')', + 'version' => '2.8.0', + ), + 'get_the_author_lastname' => array( + 'alt' => 'get_the_author_meta(\'last_name\')', + 'version' => '2.8.0', + ), + 'get_the_author_login' => array( + 'alt' => 'get_the_author_meta(\'login\')', + 'version' => '2.8.0', + ), + 'get_the_author_msn' => array( + 'alt' => 'get_the_author_meta(\'msn\')', + 'version' => '2.8.0', + ), + 'get_the_author_nickname' => array( + 'alt' => 'get_the_author_meta(\'nickname\')', + 'version' => '2.8.0', + ), + 'get_the_author_url' => array( + 'alt' => 'get_the_author_meta(\'url\')', + 'version' => '2.8.0', + ), + 'get_the_author_yim' => array( + 'alt' => 'get_the_author_meta(\'yim\')', + 'version' => '2.8.0', + ), + 'js_escape' => array( + 'alt' => 'esc_js()', + 'version' => '2.8.0', + ), + 'register_sidebar_widget' => array( + 'alt' => 'wp_register_sidebar_widget()', + 'version' => '2.8.0', + ), + 'register_widget_control' => array( + 'alt' => 'wp_register_widget_control()', + 'version' => '2.8.0', + ), + 'the_author_aim' => array( + 'alt' => 'the_author_meta(\'aim\')', + 'version' => '2.8.0', + ), + 'the_author_description' => array( + 'alt' => 'the_author_meta(\'description\')', + 'version' => '2.8.0', + ), + 'the_author_email' => array( + 'alt' => 'the_author_meta(\'email\')', + 'version' => '2.8.0', + ), + 'the_author_firstname' => array( + 'alt' => 'the_author_meta(\'first_name\')', + 'version' => '2.8.0', + ), + 'the_author_icq' => array( + 'alt' => 'the_author_meta(\'icq\')', + 'version' => '2.8.0', + ), + 'the_author_ID' => array( + 'alt' => 'the_author_meta(\'ID\')', + 'version' => '2.8.0', + ), + 'the_author_lastname' => array( + 'alt' => 'the_author_meta(\'last_name\')', + 'version' => '2.8.0', + ), + 'the_author_login' => array( + 'alt' => 'the_author_meta(\'login\')', + 'version' => '2.8.0', + ), + 'the_author_msn' => array( + 'alt' => 'the_author_meta(\'msn\')', + 'version' => '2.8.0', + ), + 'the_author_nickname' => array( + 'alt' => 'the_author_meta(\'nickname\')', + 'version' => '2.8.0', + ), + 'the_author_url' => array( + 'alt' => 'the_author_meta(\'url\')', + 'version' => '2.8.0', + ), + 'the_author_yim' => array( + 'alt' => 'the_author_meta(\'yim\')', + 'version' => '2.8.0', + ), + 'unregister_sidebar_widget' => array( + 'alt' => 'wp_unregister_sidebar_widget()', + 'version' => '2.8.0', + ), + 'unregister_widget_control' => array( + 'alt' => 'wp_unregister_widget_control()', + 'version' => '2.8.0', + ), + 'wp_specialchars' => array( + 'alt' => 'esc_html()', + 'version' => '2.8.0', + ), + + // WP 2.9.0. + '_c' => array( + 'alt' => '_x()', + 'version' => '2.9.0', + ), + '_nc' => array( + 'alt' => '_nx()', + 'version' => '2.9.0', + ), + 'get_real_file_to_edit' => array( + 'alt' => '', + 'version' => '2.9.0', + ), + 'make_url_footnote' => array( + 'alt' => '', + 'version' => '2.9.0', + ), + 'the_content_rss' => array( + 'alt' => 'the_content_feed()', + 'version' => '2.9.0', + ), + 'translate_with_context' => array( + 'alt' => '_x()', + 'version' => '2.9.0', + ), + + // WP 3.0.0. + 'activate_sitewide_plugin' => array( + 'alt' => 'activate_plugin()', + 'version' => '3.0.0', + ), + 'add_option_update_handler' => array( + 'alt' => 'register_setting()', + 'version' => '3.0.0', + ), + 'automatic_feed_links' => array( + 'alt' => 'add_theme_support( \'automatic-feed-links\' )', + 'version' => '3.0.0', + ), + 'clean_url' => array( + 'alt' => 'esc_url()', + 'version' => '3.0.0', + ), + 'clear_global_post_cache' => array( + 'alt' => 'clean_post_cache()', + 'version' => '3.0.0', + ), + 'codepress_footer_js' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'codepress_get_lang' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'deactivate_sitewide_plugin' => array( + 'alt' => 'deactivate_plugin()', + 'version' => '3.0.0', + ), + 'delete_usermeta' => array( + 'alt' => 'delete_user_meta()', + 'version' => '3.0.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'funky_javascript_callback' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'funky_javascript_fix' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'generate_random_password' => array( + 'alt' => 'wp_generate_password()', + 'version' => '3.0.0', + ), + 'get_alloptions' => array( + 'alt' => 'wp_load_alloptions()', + 'version' => '3.0.0', + ), + 'get_blog_list' => array( + 'alt' => 'wp_get_sites()', + 'version' => '3.0.0', + ), + 'get_most_active_blogs' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'get_profile' => array( + 'alt' => 'get_the_author_meta()', + 'version' => '3.0.0', + ), + 'get_user_details' => array( + 'alt' => 'get_user_by()', + 'version' => '3.0.0', + ), + 'get_usermeta' => array( + 'alt' => 'get_user_meta()', + 'version' => '3.0.0', + ), + 'get_usernumposts' => array( + 'alt' => 'count_user_posts()', + 'version' => '3.0.0', + ), + 'graceful_fail' => array( + 'alt' => 'wp_die()', + 'version' => '3.0.0', + ), + // Verified version & alternative. + 'install_blog_defaults' => array( + 'alt' => 'wp_install_defaults', + 'version' => '3.0.0', + ), + 'is_main_blog' => array( + 'alt' => 'is_main_site()', + 'version' => '3.0.0', + ), + 'is_site_admin' => array( + 'alt' => 'is_super_admin()', + 'version' => '3.0.0', + ), + 'is_taxonomy' => array( + 'alt' => 'taxonomy_exists()', + 'version' => '3.0.0', + ), + 'is_term' => array( + 'alt' => 'term_exists()', + 'version' => '3.0.0', + ), + 'is_wpmu_sitewide_plugin' => array( + 'alt' => 'is_network_only_plugin()', + 'version' => '3.0.0', + ), + 'mu_options' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'remove_option_update_handler' => array( + 'alt' => 'unregister_setting()', + 'version' => '3.0.0', + ), + 'set_current_user' => array( + 'alt' => 'wp_set_current_user()', + 'version' => '3.0.0', + ), + 'update_usermeta' => array( + 'alt' => 'update_user_meta()', + 'version' => '3.0.0', + ), + 'use_codepress' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'validate_email' => array( + 'alt' => 'is_email()', + 'version' => '3.0.0', + ), + 'wp_dropdown_cats' => array( + 'alt' => 'wp_dropdown_categories()', + 'version' => '3.0.0', + ), + 'wp_shrink_dimensions' => array( + 'alt' => 'wp_constrain_dimensions()', + 'version' => '3.0.0', + ), + 'wpmu_checkAvailableSpace' => array( + 'alt' => 'is_upload_space_available()', + 'version' => '3.0.0', + ), + 'wpmu_menu' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + + // WP 3.1.0. + 'get_author_user_ids' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', + ), + 'get_dashboard_blog' => array( + 'alt' => 'get_site()', + 'version' => '3.1.0', + ), + 'get_editable_authors' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', + ), + 'get_editable_user_ids' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', + ), + 'get_nonauthor_user_ids' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', + ), + 'get_others_drafts' => array( + 'alt' => '', + 'version' => '3.1.0', + ), + 'get_others_pending' => array( + 'alt' => '', + 'version' => '3.1.0', + ), + 'get_others_unpublished_posts' => array( + 'alt' => '', + 'version' => '3.1.0', + ), + 'get_users_of_blog' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', + ), + 'install_themes_feature_list' => array( + 'alt' => 'get_theme_feature_list()', + 'version' => '3.1.0', + ), + 'is_plugin_page' => array( + // Verified correct alternative. + 'alt' => 'global $plugin_page and/or get_plugin_page_hookname() hooks', + 'version' => '3.1.0', + ), + 'update_category_cache' => array( + 'alt' => '', + 'version' => '3.1.0', + ), + + // WP 3.2.0. + 'favorite_actions' => array( + 'alt' => 'WP_Admin_Bar', + 'version' => '3.2.0', + ), + 'wp_dashboard_quick_press_output' => array( + 'alt' => 'wp_dashboard_quick_press()', + 'version' => '3.2.0', + ), + 'wp_timezone_supported' => array( + 'alt' => '', + 'version' => '3.2.0', + ), + + // WP 3.3.0. + 'add_contextual_help' => array( + 'alt' => 'get_current_screen()->add_help_tab()', + 'version' => '3.3.0', + ), + 'get_boundary_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'get_index_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'get_parent_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'get_user_by_email' => array( + 'alt' => 'get_user_by(\'email\')', + 'version' => '3.3.0', + ), + 'get_user_metavalues' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'get_userdatabylogin' => array( + 'alt' => 'get_user_by(\'login\')', + 'version' => '3.3.0', + ), + 'index_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'is_blog_user' => array( + 'alt' => 'is_user_member_of_blog()', + 'version' => '3.3.0', + ), + 'media_upload_audio' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'media_upload_file' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'media_upload_image' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'media_upload_video' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'parent_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'sanitize_user_object' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'screen_layout' => array( + 'alt' => '$current_screen->render_screen_layout()', + 'version' => '3.3.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'screen_meta' => array( + 'alt' => '$current_screen->render_screen_meta()', + 'version' => '3.3.0', + ), + 'screen_options' => array( + 'alt' => '$current_screen->render_per_page_options()', + 'version' => '3.3.0', + ), + 'start_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'the_editor' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', + ), + 'type_url_form_audio' => array( + 'alt' => 'wp_media_insert_url_form(\'audio\')', + 'version' => '3.3.0', + ), + 'type_url_form_file' => array( + 'alt' => 'wp_media_insert_url_form(\'file\')', + 'version' => '3.3.0', + ), + 'type_url_form_image' => array( + 'alt' => 'wp_media_insert_url_form(\'image\')', + 'version' => '3.3.0', + ), + 'type_url_form_video' => array( + 'alt' => 'wp_media_insert_url_form(\'video\')', + 'version' => '3.3.0', + ), + 'wp_admin_bar_dashboard_view_site_menu' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'wp_preload_dialogs' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', + ), + 'wp_print_editor_js' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', + ), + 'wp_quicktags' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', + ), + 'wp_tiny_mce' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', + ), + 'wpmu_admin_do_redirect' => array( + 'alt' => 'wp_redirect()', + 'version' => '3.3.0', + ), + 'wpmu_admin_redirect_add_updated_param' => array( + 'alt' => 'add_query_arg()', + 'version' => '3.3.0', + ), + + // WP 3.4.0. + 'add_custom_background' => array( + 'alt' => 'add_theme_support( \'custom-background\', $args )', + 'version' => '3.4.0', + ), + 'add_custom_image_header' => array( + 'alt' => 'add_theme_support( \'custom-header\', $args )', + 'version' => '3.4.0', + ), + 'clean_page_cache' => array( + 'alt' => 'clean_post_cache()', + 'version' => '3.4.0', + ), + 'clean_pre' => array( + 'alt' => '', + 'version' => '3.4.0', + ), + 'current_theme_info' => array( + 'alt' => 'wp_get_theme()', + 'version' => '3.4.0', + ), + 'debug_fclose' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'debug_fopen' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'debug_fwrite' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'display_theme' => array( + 'alt' => '', + 'version' => '3.4.0', + ), + 'get_allowed_themes' => array( + 'alt' => 'wp_get_themes( array( \'allowed\' => true ) )', + 'version' => '3.4.0', + ), + 'get_broken_themes' => array( + 'alt' => 'wp_get_themes( array( \'errors\' => true )', + 'version' => '3.4.0', + ), + 'get_current_theme' => array( + 'alt' => 'wp_get_theme()', + 'version' => '3.4.0', + ), + 'get_site_allowed_themes' => array( + 'alt' => 'WP_Theme::get_allowed_on_network()', + 'version' => '3.4.0', + ), + 'get_theme' => array( + 'alt' => 'wp_get_theme( $stylesheet )', + 'version' => '3.4.0', + ), + 'get_theme_data' => array( + 'alt' => 'wp_get_theme()', + 'version' => '3.4.0', + ), + 'get_themes' => array( + 'alt' => 'wp_get_themes()', + 'version' => '3.4.0', + ), + 'logIO' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'remove_custom_background' => array( + 'alt' => 'remove_theme_support( \'custom-background\' )', + 'version' => '3.4.0', + ), + 'remove_custom_image_header' => array( + 'alt' => 'remove_theme_support( \'custom-header\' )', + 'version' => '3.4.0', + ), + 'update_page_cache' => array( + 'alt' => 'update_post_cache()', + 'version' => '3.4.0', + ), + 'wpmu_get_blog_allowedthemes' => array( + 'alt' => 'WP_Theme::get_allowed_on_site()', + 'version' => '3.4.0', + ), + + // WP 3.4.1. + 'wp_explain_nonce' => array( + 'alt' => 'wp_nonce_ays()', + 'version' => '3.4.1', + ), + + // WP 3.5.0. + '_flip_image_resource' => array( + 'alt' => 'WP_Image_Editor::flip()', + 'version' => '3.5.0', + ), + '_get_post_ancestors' => array( + 'alt' => '', + 'version' => '3.5.0', + ), + '_insert_into_post_button' => array( + 'alt' => '', + 'version' => '3.5.0', + ), + '_media_button' => array( + 'alt' => '', + 'version' => '3.5.0', + ), + '_rotate_image_resource' => array( + 'alt' => 'WP_Image_Editor::rotate()', + 'version' => '3.5.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + '_save_post_hook' => array( + 'alt' => '', + 'version' => '3.5.0', + ), + 'gd_edit_image_support' => array( + 'alt' => 'wp_image_editor_supports()', + 'version' => '3.5.0', + ), + 'get_default_page_to_edit' => array( + 'alt' => 'get_default_post_to_edit( \'page\' )', + 'version' => '3.5.0', + ), + 'get_post_to_edit' => array( + 'alt' => 'get_post()', + 'version' => '3.5.0', + ), + 'get_udims' => array( + 'alt' => 'wp_constrain_dimensions()', + 'version' => '3.5.0', + ), + 'image_resize' => array( + 'alt' => 'wp_get_image_editor()', + 'version' => '3.5.0', + ), + 'sticky_class' => array( + 'alt' => 'post_class()', + 'version' => '3.5.0', + ), + 'user_pass_ok' => array( + 'alt' => 'wp_authenticate()', + 'version' => '3.5.0', + ), + 'wp_cache_reset' => array( + 'alt' => 'wp_cache_switch_to_blog()', + 'version' => '3.5.0', + ), + 'wp_create_thumbnail' => array( + 'alt' => 'image_resize()', + 'version' => '3.5.0', + ), + 'wp_get_single_post' => array( + 'alt' => 'get_post()', + 'version' => '3.5.0', + ), + 'wp_load_image' => array( + 'alt' => 'wp_get_image_editor()', + 'version' => '3.5.0', + ), + + // WP 3.6.0. + 'get_user_id_from_string' => array( + 'alt' => 'get_user_by()', + 'version' => '3.6.0', + ), + 'wp_convert_bytes_to_hr' => array( + 'alt' => 'size_format()', + 'version' => '3.6.0', + ), + 'wp_nav_menu_locations_meta_box' => array( + 'alt' => '', + 'version' => '3.6.0', + ), + + // WP 3.7.0. + '_search_terms_tidy' => array( + 'alt' => '', + 'version' => '3.7.0', + ), + 'get_blogaddress_by_domain' => array( + 'alt' => '', + 'version' => '3.7.0', + ), + 'the_attachment_links' => array( + 'alt' => '', + 'version' => '3.7.0', + ), + 'wp_update_core' => array( + 'alt' => 'new Core_Upgrader();', + 'version' => '3.7.0', + ), + 'wp_update_plugin' => array( + 'alt' => 'new Plugin_Upgrader();', + 'version' => '3.7.0', + ), + 'wp_update_theme' => array( + 'alt' => 'new Theme_Upgrader();', + 'version' => '3.7.0', + ), + + // WP 3.8.0. + 'get_screen_icon' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + 'screen_icon' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_incoming_links' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_incoming_links_control' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_incoming_links_output' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_plugins' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_primary_control' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_recent_comments_control' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_secondary' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_secondary_control' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_dashboard_secondary_output' => array( + 'alt' => '', + 'version' => '3.8.0', + ), + + // WP 3.9.0. + '_relocate_children' => array( + 'alt' => '', + 'version' => '3.9.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'default_topic_count_text' => array( + 'alt' => '', + 'version' => '3.9.0', + ), + 'format_to_post' => array( + 'alt' => '', + 'version' => '3.9.0', + ), + 'get_current_site_name' => array( + 'alt' => 'get_current_site()', + 'version' => '3.9.0', + ), + 'rich_edit_exists' => array( + 'alt' => '', + 'version' => '3.9.0', + ), + 'wpmu_current_site' => array( + 'alt' => '', + 'version' => '3.9.0', + ), + + // WP 4.0.0. + 'get_all_category_ids' => array( + 'alt' => 'get_terms()', + 'version' => '4.0.0', + ), + 'like_escape' => array( + 'alt' => 'wpdb::esc_like()', + 'version' => '4.0.0', + ), + 'url_is_accessable_via_ssl' => array( + 'alt' => '', + 'version' => '4.0.0', + ), + + // WP 4.1.0. + // This is a method from the WP_Customize_Image_Control class. See #731. + 'add_tab' => array( + 'alt' => '', + 'version' => '4.1.0', + ), + // This is a method from the WP_Customize_Image_Control class. See #731. + 'prepare_control' => array( + 'alt' => '', + 'version' => '4.1.0', + ), + // This is a method from the WP_Customize_Image_Control class. See #731. + 'print_tab_image' => array( + 'alt' => '', + 'version' => '4.1.0', + ), + // This is a method from the WP_Customize_Image_Control class. See #731. + 'remove_tab' => array( + 'alt' => '', + 'version' => '4.1.0', + ), + + // WP 4.2.0. + // This is a method from the WP_Customize_Widgets class. See #731. + 'prepreview_added_sidebars_widgets' => array( + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', + ), + // This is a method from the WP_Customize_Widgets class. See #731. + 'prepreview_added_widget_instance' => array( + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', + ), + // This is a method from the WP_Customize_Widgets class. See #731. + 'remove_prepreview_filters' => array( + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', + ), + // This is a method from the WP_Customize_Widgets class. See #731. + 'setup_widget_addition_previews' => array( + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', + ), + + // WP 4.3.0. + '_preview_theme_stylesheet_filter' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + '_preview_theme_template_filter' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + 'preview_theme' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + 'preview_theme_ob_filter' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + 'preview_theme_ob_filter_callback' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + // Verified; see https://core.trac.wordpress.org/ticket/41121, patch 3. + 'wp_ajax_wp_fullscreen_save_post' => array( + 'alt' => '', + 'version' => '4.3.0', + ), + 'wp_htmledit_pre' => array( + 'alt' => 'format_for_editor()', + 'version' => '4.3.0', + ), + 'wp_richedit_pre' => array( + 'alt' => 'format_for_editor()', + 'version' => '4.3.0', + ), + + // WP 4.4.0. + 'create_empty_blog' => array( + 'alt' => '', + 'version' => '4.4.0', + ), + 'force_ssl_login' => array( + 'alt' => 'force_ssl_admin()', + 'version' => '4.4.0', + ), + 'get_admin_users_for_domain' => array( + 'alt' => '', + 'version' => '4.4.0', + ), + 'post_permalink' => array( + 'alt' => 'get_permalink()', + 'version' => '4.4.0', + ), + 'wp_get_http' => array( + 'alt' => 'the WP_Http class', + 'version' => '4.4.0', + ), + // This is a method i.e. WP_Widget_Recent_Comments::flush_widget_cache() See #731. + 'flush_widget_cache' => array( + 'alt' => '', + 'version' => '4.4.0', + ), + + // WP 4.5.0. + 'add_object_page' => array( + 'alt' => 'add_menu_page()', + 'version' => '4.5.0', + ), + 'add_utility_page' => array( + 'alt' => 'add_menu_page()', + 'version' => '4.5.0', + ), + 'comments_popup_script' => array( + 'alt' => '', + 'version' => '4.5.0', + ), + 'get_comments_popup_template' => array( + 'alt' => '', + 'version' => '4.5.0', + ), + 'get_currentuserinfo' => array( + 'alt' => 'wp_get_current_user()', + 'version' => '4.5.0', + ), + 'is_comments_popup' => array( + 'alt' => '', + 'version' => '4.5.0', + ), + 'popuplinks' => array( + 'alt' => '', + 'version' => '4.5.0', + ), + + // WP 4.6.0. + 'post_form_autocomplete_off' => array( + 'alt' => '', + 'version' => '4.6.0', + ), + 'wp_embed_handler_googlevideo' => array( + 'alt' => '', + 'version' => '4.6.0', + ), + 'wp_get_sites' => array( + 'alt' => 'get_sites()', + 'version' => '4.6.0', + ), + + // WP 4.7.0. + '_sort_nav_menu_items' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + '_usort_terms_by_ID' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + '_usort_terms_by_name' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + 'get_paged_template' => array( + 'alt' => '', + 'version' => '4.7.0', + ), + 'wp_get_network' => array( + 'alt' => 'get_network()', + 'version' => '4.7.0', + ), + 'wp_kses_js_entities' => array( + 'alt' => '', + 'version' => '4.7.0', + ), + + // WP 4.8.0. + 'wp_dashboard_plugins_output' => array( + 'alt' => '', + 'version' => '4.8.0', + ), + + // WP 4.9.0. + 'get_shortcut_link' => array( + 'alt' => '', + 'version' => '4.9.0', + ), + 'is_user_option_local' => array( + 'alt' => '', + 'version' => '4.9.0', + ), + 'wp_ajax_press_this_add_category' => array( + 'alt' => '', + 'version' => '4.9.0', + ), + 'wp_ajax_press_this_save_post' => array( + 'alt' => '', + 'version' => '4.9.0', + ), + + // WP 5.1.0. + 'insert_blog' => array( + 'alt' => 'wp_insert_site()', + 'version' => '5.1.0', + ), + 'install_blog' => array( + 'alt' => '', + 'version' => '5.1.0', + ), + + // WP 5.3.0. + '_wp_json_prepare_data' => array( + 'alt' => '', + 'version' => '5.3.0', + ), + '_wp_privacy_requests_screen_options' => array( + 'alt' => '', + 'version' => '5.3.0', + ), + 'update_user_status' => array( + 'alt' => 'wp_update_user()', + 'version' => '5.3.0', + ), + + // WP 5.4.0. + 'wp_get_user_request_data' => array( + 'alt' => 'wp_get_user_request()', + 'version' => '5.4.0', + ), + + // WP 5.5.0. + '_wp_register_meta_args_whitelist' => array( + 'alt' => '_wp_register_meta_args_allowed_list()', + 'version' => '5.5.0', + ), + 'add_option_whitelist' => array( + 'alt' => 'add_allowed_options()', + 'version' => '5.5.0', + ), + 'remove_option_whitelist' => array( + 'alt' => 'remove_allowed_options()', + 'version' => '5.5.0', + ), + 'wp_blacklist_check' => array( + 'alt' => 'wp_check_comment_disallowed_list()', + 'version' => '5.5.0', + ), + 'wp_make_content_images_responsive' => array( + 'alt' => 'wp_filter_content_tags()', + 'version' => '5.5.0', + ), + 'wp_unregister_GLOBALS' => array( + 'alt' => '', + 'version' => '5.5.0', + ), + + // WP 5.7.0. + 'noindex' => array( + 'alt' => 'wp_robots_noindex()', + 'version' => '5.7.0', + ), + 'wp_no_robots' => array( + 'alt' => 'wp_robots_no_robots()', + 'version' => '5.7.0', + ), + 'wp_sensitive_page_meta' => array( + 'alt' => 'wp_robots_sensitive_page()', + 'version' => '5.7.0', + ), + + // WP 5.8.0. + '_excerpt_render_inner_columns_blocks' => array( + 'alt' => '_excerpt_render_inner_blocks()', + 'version' => '5.8.0', + ), + + // WP 5.9.0. + 'readonly' => array( + 'alt' => 'wp_readonly()', + 'version' => '5.9.0', + ), + + // WP 5.9.1. + 'wp_render_duotone_filter_preset' => array( + 'alt' => 'wp_get_duotone_filter_property()', + 'version' => '5.9.1', + ), + + // WP 6.0.0. + 'image_attachment_fields_to_save' => array( + 'alt' => '', + 'version' => '6.0.0', + ), + 'wp_add_iframed_editor_assets_html' => array( + 'alt' => '', + 'version' => '6.0.0', + ), + 'wp_skip_border_serialization' => array( + 'alt' => 'wp_should_skip_block_supports_serialization()', + 'version' => '6.0.0', + ), + 'wp_skip_dimensions_serialization' => array( + 'alt' => 'wp_should_skip_block_supports_serialization()', + 'version' => '6.0.0', + ), + 'wp_skip_spacing_serialization' => array( + 'alt' => 'wp_should_skip_block_supports_serialization()', + 'version' => '6.0.0', + ), + + // WP 6.0.2. + 'the_meta' => array( + 'alt' => 'get_post_meta()', + 'version' => '6.0.2', + ), + + // WP 6.0.3. + // Verified; see https://core.trac.wordpress.org/ticket/56791#comment:10. + '_filter_query_attachment_filenames' => array( + 'alt' => 'add_filter( "wp_allow_query_attachment_by_filename", "__return_true" )', + 'version' => '6.0.3', + ), + + // WP 6.1.0. + '_get_path_to_translation' => array( + 'alt' => 'WP_Textdomain_Registry', + 'version' => '6.1.0', + ), + '_get_path_to_translation_from_lang_dir' => array( + 'alt' => 'WP_Textdomain_Registry', + 'version' => '6.1.0', + ), + '_wp_multiple_block_styles' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'global_terms' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'global_terms_enabled' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'install_global_terms' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'sync_category_tag_slugs' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'wp_get_attachment_thumb_file' => array( + 'alt' => '', + 'version' => '6.1.0', + ), + 'wp_typography_get_css_variable_inline_style' => array( + 'alt' => 'wp_style_engine_get_styles()', + 'version' => '6.1.0', + ), + + // WP 6.2.0. + '_resolve_home_block_template' => array( + 'alt' => '', + 'version' => '6.2.0', + ), + 'get_page_by_title' => array( + 'alt' => 'WP_Query', + 'version' => '6.2.0', + ), + + // WP 6.3.0. + '_wp_tinycolor_bound_alpha' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'block_core_navigation_get_classic_menu_fallback' => array( + 'alt' => 'WP_Navigation_Fallback::get_classic_menu_fallback', + 'version' => '6.3.0', + ), + 'block_core_navigation_get_classic_menu_fallback_blocks' => array( + 'alt' => 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks', + 'version' => '6.3.0', + ), + 'block_core_navigation_get_most_recently_published_navigation' => array( + 'alt' => 'WP_Navigation_Fallback::get_most_recently_published_navigation', + 'version' => '6.3.0', + ), + 'block_core_navigation_maybe_use_classic_menu_fallback' => array( + 'alt' => 'WP_Navigation_Fallback::create_classic_menu_fallback', + 'version' => '6.3.0', + ), + 'block_core_navigation_parse_blocks_from_menu_items' => array( + 'alt' => 'WP_Navigation_Fallback::parse_blocks_from_menu_items', + 'version' => '6.3.0', + ), + 'block_core_navigation_submenu_build_css_colors' => array( + 'alt' => 'wp_apply_colors_support()', + 'version' => '6.3.0', + ), + 'wlwmanifest_link' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_get_duotone_filter_id' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_get_duotone_filter_property' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_get_duotone_filter_svg' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_get_global_styles_svg_filters' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_get_loading_attr_default' => array( + 'alt' => 'wp_get_loading_optimization_attributes()', + 'version' => '6.3.0', + ), + 'wp_global_styles_render_svg_filters' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_img_tag_add_loading_attr' => array( + 'alt' => 'wp_img_tag_add_loading_optimization_attrs()', + 'version' => '6.3.0', + ), + 'wp_queue_comments_for_comment_meta_lazyload' => array( + 'alt' => 'wp_lazyload_comment_meta()', + 'version' => '6.3.0', + ), + 'wp_register_duotone_support' => array( + 'alt' => 'WP_Duotone::register_duotone_support()', + 'version' => '6.3.0', + ), + 'wp_render_duotone_support' => array( + 'alt' => 'WP_Duotone::render_duotone_support()', + 'version' => '6.3.0', + ), + 'wp_tinycolor_bound01' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_tinycolor_hsl_to_rgb' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_tinycolor_hue_to_rgb' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_tinycolor_rgb_to_rgb' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + 'wp_tinycolor_string_to_rgb' => array( + 'alt' => '', + 'version' => '6.3.0', + ), + ); + + /** + * Groups of functions to restrict. + * + * @return array + */ + public function getGroups() { + // Make sure all array keys are lowercase. + $this->deprecated_functions = array_change_key_case( $this->deprecated_functions, \CASE_LOWER ); + + return array( + 'deprecated_functions' => array( + 'functions' => array_keys( $this->deprecated_functions ), + ), + ); + } + + /** + * Process a matched token. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. Will + * always be 'deprecated_functions'. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $this->set_minimum_wp_version(); + + $message = '%s() has been deprecated since WordPress version %s.'; + $data = array( + $this->tokens[ $stackPtr ]['content'], + $this->deprecated_functions[ $matched_content ]['version'], + ); + + if ( ! empty( $this->deprecated_functions[ $matched_content ]['alt'] ) ) { + $message .= ' Use %s instead.'; + $data[] = $this->deprecated_functions[ $matched_content ]['alt']; + } + + MessageHelper::addMessage( + $this->phpcsFile, + $message, + $stackPtr, + ( $this->wp_version_compare( $this->deprecated_functions[ $matched_content ]['version'], $this->minimum_wp_version, '<' ) ), + MessageHelper::stringToErrorcode( $matched_content . 'Found' ), + $data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php new file mode 100644 index 00000000..0806772b --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php @@ -0,0 +1,291 @@ + array( + * (int) Target parameter position, 1-based. => array( + * (string) 'name' => (string|array) Parameter name(s), + * (string) 'values' => array( + * (string) Parameter value. => array( + * 'alt' => (string) Suggested alternative. + * 'version' => (int) The WordPress version when deprecated. + * ) + * ) + * ) + * ) + * ); + */ + protected $target_functions = array( + 'add_option' => array( + 1 => array( + 'name' => 'option', + 'values' => array( + 'blacklist_keys' => array( + 'alt' => 'disallowed_keys', + 'version' => '5.5.0', + ), + 'comment_whitelist' => array( + 'alt' => 'comment_previously_approved', + 'version' => '5.5.0', + ), + ), + ), + ), + 'add_settings_field' => array( + 4 => array( + 'name' => 'page', + 'values' => array( + 'misc' => array( + 'alt' => 'another settings group', + 'version' => '3.0.0', + ), + 'privacy' => array( + 'alt' => 'another settings group', + 'version' => '3.5.0', + ), + ), + ), + ), + 'add_settings_section' => array( + 4 => array( + 'name' => 'page', + 'values' => array( + 'misc' => array( + 'alt' => 'another settings group', + 'version' => '3.0.0', + ), + 'privacy' => array( + 'alt' => 'another settings group', + 'version' => '3.5.0', + ), + ), + ), + ), + 'bloginfo' => array( + 1 => array( + 'name' => 'show', + 'values' => array( + 'home' => array( + 'alt' => 'the "url" argument', + 'version' => '2.2.0', + ), + 'siteurl' => array( + 'alt' => 'the "url" argument', + 'version' => '2.2.0', + ), + 'text_direction' => array( + 'alt' => 'is_rtl()', + 'version' => '2.2.0', + ), + ), + ), + ), + 'get_bloginfo' => array( + 1 => array( + 'name' => 'show', + 'values' => array( + 'home' => array( + 'alt' => 'the "url" argument', + 'version' => '2.2.0', + ), + 'siteurl' => array( + 'alt' => 'the "url" argument', + 'version' => '2.2.0', + ), + 'text_direction' => array( + 'alt' => 'is_rtl()', + 'version' => '2.2.0', + ), + ), + ), + ), + 'get_option' => array( + 1 => array( + 'name' => 'option', + 'values' => array( + 'blacklist_keys' => array( + 'alt' => 'disallowed_keys', + 'version' => '5.5.0', + ), + 'comment_whitelist' => array( + 'alt' => 'comment_previously_approved', + 'version' => '5.5.0', + ), + ), + ), + ), + 'register_setting' => array( + 1 => array( + 'name' => 'option_group', + 'values' => array( + 'misc' => array( + 'alt' => 'another settings group', + 'version' => '3.0.0', + ), + 'privacy' => array( + 'alt' => 'another settings group', + 'version' => '3.5.0', + ), + ), + ), + ), + 'unregister_setting' => array( + 1 => array( + 'name' => 'option_group', + 'values' => array( + 'misc' => array( + 'alt' => 'another settings group', + 'version' => '3.0.0', + ), + 'privacy' => array( + 'alt' => 'another settings group', + 'version' => '3.5.0', + ), + ), + ), + ), + 'update_option' => array( + 1 => array( + 'name' => 'option', + 'values' => array( + 'blacklist_keys' => array( + 'alt' => 'disallowed_keys', + 'version' => '5.5.0', + ), + 'comment_whitelist' => array( + 'alt' => 'comment_previously_approved', + 'version' => '5.5.0', + ), + ), + ), + ), + ); + + /** + * Process the parameters of a matched function. + * + * @since 1.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $this->set_minimum_wp_version(); + + foreach ( $this->target_functions[ $matched_content ] as $position => $parameter_args ) { + $found_param = PassedParameters::getParameterFromStack( $parameters, $position, $parameter_args['name'] ); + + // Skip if the parameter was not found. + if ( false === $found_param ) { + continue; + } + + $this->process_parameter( $matched_content, $found_param, $parameter_args['values'] ); + } + } + + /** + * Process the parameter of a matched function. + * + * @since 1.0.0 + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameter Array with start and end token positon of the parameter. + * @param array $parameter_args Array with alternative and WordPress deprecation version of the parameter. + * + * @return void + */ + protected function process_parameter( $matched_content, $parameter, $parameter_args ) { + + $parameter_position = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $parameter['start'], + $parameter['end'] + 1, + true + ); + + if ( false === $parameter_position ) { + return; + } + + $matched_parameter = TextStrings::stripQuotes( $this->tokens[ $parameter_position ]['content'] ); + if ( ! isset( $parameter_args[ $matched_parameter ] ) ) { + return; + } + + $message = 'The parameter value "%s" has been deprecated since WordPress version %s.'; + $data = array( + $matched_parameter, + $parameter_args[ $matched_parameter ]['version'], + ); + + if ( ! empty( $parameter_args[ $matched_parameter ]['alt'] ) ) { + $message .= ' Use %s instead.'; + $data[] = $parameter_args[ $matched_parameter ]['alt']; + } + + $is_error = $this->wp_version_compare( $parameter_args[ $matched_parameter ]['version'], $this->minimum_wp_version, '<' ); + MessageHelper::addMessage( + $this->phpcsFile, + $message, + $parameter_position, + $is_error, + 'Found', + $data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParametersSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParametersSniff.php new file mode 100644 index 00000000..2675ecfa --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DeprecatedParametersSniff.php @@ -0,0 +1,509 @@ + value + * in a custom ruleset. + * + * @uses \WordPressCS\WordPress\Helpers\MinimumWPVersionTrait::$minimum_wp_version + */ +final class DeprecatedParametersSniff extends AbstractFunctionParameterSniff { + + use MinimumWPVersionTrait; + + /** + * The group name for this group of functions. + * + * @since 0.12.0 + * + * @var string + */ + protected $group_name = 'wp_deprecated_parameters'; + + /** + * Array of function, argument, and default value for deprecated argument. + * + * The functions are ordered alphabetically. + * Last updated for WordPress 6.3. + * + * @since 0.12.0 + * + * @var array Multidimensional array with parameter details. + * $target_functions = array( + * (string) Function name. => array( + * (int) Target parameter position, 1-based. => array( + * 'name' => (string|array) Parameter name or list of names if the parameter + * was renamed since the release of PHP 8.0. + * 'value' => (mixed) Expected default value for the deprecated parameter. + * Currently the default values: true, false, null, empty arrays + * and both empty and non-empty strings can be handled correctly + * by the process_parameters() method. + * When an additional default value is added, the relevant code + * in the process_parameters() method will need to be adjusted. + * 'version' => (int) The WordPress version when deprecated. + * ) + * ) + * ); + */ + protected $target_functions = array( + '_future_post_hook' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '2.3.0', + ), + ), + '_load_remote_block_patterns' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '5.9.0', + ), + ), + '_wp_post_revision_fields' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '4.5.0', + ), + ), + 'add_option' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.3.0', + ), + ), + 'comments_link' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '0.72', + ), + 2 => array( + 'name' => 'deprecated_2', + 'value' => '', + 'version' => '1.3.0', + ), + ), + 'convert_chars' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '0.71', + ), + ), + 'delete_plugins' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '4.0.0', + ), + ), + 'discover_pingback_server_uri' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.7.0', + ), + ), + 'get_blog_list' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', // Was previously part of MU. + ), + ), + 'get_category_parents' => array( + 5 => array( + 'name' => 'deprecated', + 'value' => array(), + 'version' => '4.8.0', + ), + ), + 'get_delete_post_link' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'get_last_updated' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', // Was previously part of MU. + ), + ), + 'get_site_option' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => true, + 'version' => '4.4.0', + ), + ), + 'get_terms' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '4.5.0', + ), + ), + 'get_the_author' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.1.0', + ), + ), + 'get_user_option' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'get_wp_title_rss' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '–', + 'version' => '4.4.0', + ), + ), + 'global_terms' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '6.1.0', + ), + ), + 'iframe_header' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '4.2.0', + ), + ), + 'install_search_form' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => true, + 'version' => '4.6.0', + ), + ), + 'is_email' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '3.0.0', + ), + ), + 'load_plugin_textdomain' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '2.7.0', + ), + ), + 'newblog_notify_siteadmin' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'permalink_single_rss' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.3.0', + ), + ), + 'redirect_this_site' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'register_meta' => array( + 4 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '4.6.0', + ), + ), + 'safecss_filter_attr' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.8.1', + ), + ), + 'switch_to_blog' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '3.5.0', // Was previously part of MU. + ), + ), + 'term_description' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '4.9.2', + ), + ), + 'the_attachment_link' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '2.5.0', + ), + ), + 'the_author' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.1.0', + ), + 2 => array( + 'name' => 'deprecated_echo', + 'value' => true, + 'version' => '1.5.0', + ), + ), + 'the_author_posts_link' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.1.0', + ), + ), + 'trackback_rdf' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.5.0', + ), + ), + 'trackback_url' => array( + 1 => array( + 'name' => 'deprecated_echo', + 'value' => true, + 'version' => '2.5.0', + ), + ), + 'unregister_setting' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '4.7.0', + ), + ), + 'update_blog_option' => array( + 4 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '3.1.0', + ), + ), + 'update_blog_status' => array( + 4 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '3.1.0', + ), + ), + 'update_posts_count' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'update_user_status' => array( + 4 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '3.0.2', + ), + ), + 'wp_count_terms' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '5.6.0', + ), + ), + 'wp_create_thumbnail' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '3.5.0', + ), + ), + 'wp_get_http_headers' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => false, + 'version' => '2.7.0', + ), + ), + 'wp_get_sidebars_widgets' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => true, + 'version' => '2.8.1', + ), + ), + 'wp_install' => array( + 5 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.6.0', + ), + ), + 'wp_login' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.5.0', + ), + ), + 'wp_new_user_notification' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '4.3.1', + ), + ), + 'wp_notify_postauthor' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '3.8.0', + ), + ), + 'wp_title_rss' => array( + 1 => array( + 'name' => 'deprecated', + 'value' => '–', + 'version' => '4.4.0', + ), + ), + 'wp_upload_bits' => array( + 2 => array( + 'name' => 'deprecated', + 'value' => null, + 'version' => '2.0.0', + ), + ), + 'xfn_check' => array( + 3 => array( + 'name' => 'deprecated', + 'value' => '', + 'version' => '2.5.0', + ), + ), + ); + + /** + * Process the parameters of a matched function. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + + $this->set_minimum_wp_version(); + + $paramCount = \count( $parameters ); + foreach ( $this->target_functions[ $matched_content ] as $position => $parameter_args ) { + + $found_param = PassedParameters::getParameterFromStack( $parameters, $position, $parameter_args['name'] ); + if ( false === $found_param ) { + continue; + } + + // The list will need to updated if the default value is not supported. + switch ( $found_param['raw'] ) { + case 'true': + $matched_parameter = true; + break; + case 'false': + $matched_parameter = false; + break; + case 'null': + $matched_parameter = null; + break; + case 'array()': + case '[]': + $matched_parameter = array(); + break; + default: + $matched_parameter = TextStrings::stripQuotes( $found_param['raw'] ); + break; + } + + if ( $parameter_args['value'] === $matched_parameter ) { + continue; + } + + $message = 'The parameter "%s" at position #%s of %s() has been deprecated since WordPress version %s.'; + $is_error = $this->wp_version_compare( $parameter_args['version'], $this->minimum_wp_version, '<' ); + $code = MessageHelper::stringToErrorcode( ucfirst( $matched_content ) . 'Param' . $position . 'Found' ); + + $data = array( + $found_param['raw'], + $position, + $matched_content, + $parameter_args['version'], + ); + + if ( isset( $parameter_args['value'] ) + && isset( $found_param['name'] ) === false + && $position < $paramCount + ) { + $message .= ' Use "%s" instead.'; + $data[] = (string) $parameter_args['value']; + } else { + $message .= ' Instead do not pass the parameter.'; + } + + MessageHelper::addMessage( $this->phpcsFile, $message, $stackPtr, $is_error, $code, $data, 0 ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php new file mode 100644 index 00000000..7db5a378 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php @@ -0,0 +1,160 @@ + 'get_stylesheet_directory()', + 'TEMPLATEPATH' => 'get_template_directory()', + 'PLUGINDIR' => 'WP_PLUGIN_DIR', + 'MUPLUGINDIR' => 'WPMU_PLUGIN_DIR', + 'HEADER_IMAGE' => 'add_theme_support( \'custom-header\' )', + 'NO_HEADER_TEXT' => 'add_theme_support( \'custom-header\' )', + 'HEADER_TEXTCOLOR' => 'add_theme_support( \'custom-header\' )', + 'HEADER_IMAGE_WIDTH' => 'add_theme_support( \'custom-header\' )', + 'HEADER_IMAGE_HEIGHT' => 'add_theme_support( \'custom-header\' )', + 'BACKGROUND_COLOR' => 'add_theme_support( \'custom-background\' )', + 'BACKGROUND_IMAGE' => 'add_theme_support( \'custom-background\' )', + ); + + /** + * Array of functions to check. + * + * @since 0.14.0 + * @since 3.0.0 The format of the value has changed from an integer parameter + * position to an array with the parameter position and name. + * + * @var array> Function name as key, array with target + * parameter and name as value. + */ + protected $target_functions = array( + 'define' => array( + 'position' => 1, + 'name' => 'constant_name', + ), + ); + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + if ( isset( $this->target_functions[ strtolower( $this->tokens[ $stackPtr ]['content'] ) ] ) ) { + // Disallow excluding function groups for this sniff. + $this->exclude = array(); + + return parent::process_token( $stackPtr ); + + } else { + return $this->process_arbitrary_tstring( $stackPtr ); + } + } + + /** + * Process an arbitrary T_STRING token to determine whether it is one of the target constants. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_arbitrary_tstring( $stackPtr ) { + $content = $this->tokens[ $stackPtr ]['content']; + + if ( ! isset( $this->discouraged_constants[ $content ] ) ) { + return; + } + + if ( ConstantsHelper::is_use_of_global_constant( $this->phpcsFile, $stackPtr ) === false ) { + return; + } + + $this->phpcsFile->addWarning( + 'Found usage of constant "%s". Use %s instead.', + $stackPtr, + MessageHelper::stringToErrorcode( $content . 'UsageFound' ), + array( + $content, + $this->discouraged_constants[ $content ], + ) + ); + } + + /** + * Process the parameters of a matched `define` function call. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $target_param = $this->target_functions[ $matched_content ]; + + // Was the target parameter passed ? + $found_param = PassedParameters::getParameterFromStack( $parameters, $target_param['position'], $target_param['name'] ); + if ( false === $found_param ) { + return; + } + + $clean_content = TextStrings::stripQuotes( $found_param['clean'] ); + + if ( isset( $this->discouraged_constants[ $clean_content ] ) ) { + $first_non_empty = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $found_param['start'], + ( $found_param['end'] + 1 ), + true + ); + + $this->phpcsFile->addWarning( + 'Found declaration of constant "%s". Use %s instead.', + $first_non_empty, + MessageHelper::stringToErrorcode( $clean_content . 'DeclarationFound' ), + array( + $clean_content, + $this->discouraged_constants[ $clean_content ], + ) + ); + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php new file mode 100644 index 00000000..941b992b --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php @@ -0,0 +1,54 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'query_posts' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use WP_Query instead.', + 'functions' => array( + 'query_posts', + ), + ), + + 'wp_reset_query' => array( + 'type' => 'warning', + 'message' => '%s() is discouraged. Use wp_reset_postdata() instead.', + 'functions' => array( + 'wp_reset_query', + ), + ), + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php new file mode 100644 index 00000000..6e4385a9 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php @@ -0,0 +1,241 @@ + Key is function name, value irrelevant. + */ + protected $target_functions = array( + 'wp_register_script' => true, + 'wp_enqueue_script' => true, + 'wp_register_style' => true, + 'wp_enqueue_style' => true, + ); + + /** + * False + the empty tokens array. + * + * This array is enriched with the $emptyTokens array in the register() method. + * + * @var array + */ + private $false_tokens = array( + \T_FALSE => \T_FALSE, + ); + + /** + * Token codes which are "safe" to accept to determine whether a version would evaluate to `false`. + * + * This array is enriched with the several of the PHPCS token arrays in the register() method. + * + * @var array + */ + private $safe_tokens = array( + \T_NULL => \T_NULL, + \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, + \T_LNUMBER => \T_LNUMBER, + \T_DNUMBER => \T_DNUMBER, + \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING, + \T_START_NOWDOC => \T_START_NOWDOC, + \T_NOWDOC => \T_NOWDOC, + \T_END_NOWDOC => \T_END_NOWDOC, + \T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS, + \T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS, + \T_STRING_CONCAT => \T_STRING_CONCAT, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * Overloads and calls the parent method to allow for adding additional tokens to the $safe_tokens property. + * + * @return array + */ + public function register() { + $this->false_tokens += Tokens::$emptyTokens; + + $this->safe_tokens += Tokens::$emptyTokens; + $this->safe_tokens += Tokens::$assignmentTokens; + $this->safe_tokens += Tokens::$comparisonTokens; + $this->safe_tokens += Tokens::$operators; + $this->safe_tokens += Tokens::$booleanOperators; + $this->safe_tokens += Tokens::$castTokens; + + return parent::register(); + } + + /** + * Process the parameters of a matched function. + * + * @since 1.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + // Check to see if a source ($src) is specified. + $src_param = PassedParameters::getParameterFromStack( $parameters, 2, 'src' ); + if ( false === $src_param ) { + return; + } + + /* + * Version Check: Check to make sure the version is set explicitly. + */ + + $version_param = PassedParameters::getParameterFromStack( $parameters, 4, 'ver' ); + + $error_ptr = $stackPtr; + if ( false !== $version_param ) { + $error_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $version_param['start'], ( $version_param['end'] + 1 ), true ); + if ( false === $error_ptr ) { + $error_ptr = $version_param['start']; + } + } + + if ( false === $version_param || 'null' === $version_param['clean'] ) { + $type = 'script'; + if ( strpos( $matched_content, '_style' ) !== false ) { + $type = 'style'; + } + + $this->phpcsFile->addWarning( + 'Resource version not set in call to %s(). This means new versions of the %s may not always be loaded due to browser caching.', + $error_ptr, + 'MissingVersion', + array( $matched_content, $type ) + ); + // The version argument should have a non-false value. + } elseif ( $this->is_falsy( $version_param['start'], $version_param['end'] ) ) { + $this->phpcsFile->addError( + 'Version parameter is not explicitly set or has been set to an equivalent of "false" for %s; ' . + 'This means that the WordPress core version will be used which is not recommended for plugin or theme development.', + $error_ptr, + 'NoExplicitVersion', + array( $matched_content ) + ); + } + + /* + * In footer Check + * + * Check to make sure that $in_footer is set to true. + * It will warn the user to make sure it is intended. + * + * Only wp_register_script and wp_enqueue_script need this check, + * as this parameter is not available to wp_register_style and wp_enqueue_style. + */ + if ( 'wp_register_script' !== $matched_content && 'wp_enqueue_script' !== $matched_content ) { + return; + } + + $infooter_param = PassedParameters::getParameterFromStack( $parameters, 5, 'in_footer' ); + if ( false === $infooter_param ) { + // If in footer is not set, throw a warning about the default. + $this->phpcsFile->addWarning( + 'In footer ($in_footer) is not set explicitly %s; ' . + 'It is recommended to load scripts in the footer. Please set this value to `true` to load it in the footer, or explicitly `false` if it should be loaded in the header.', + $stackPtr, + 'NotInFooter', + array( $matched_content ) + ); + } + } + + /** + * Determine if a range has a falsy value. + * + * @param int $start The position to start looking from. + * @param int $end The position to stop looking (inclusive). + * + * @return bool True if the parameter is falsy. + * False if the parameter is not falsy or when it + * couldn't be reliably determined. + */ + protected function is_falsy( $start, $end ) { + + // Find anything excluding the false tokens. + $has_non_false = $this->phpcsFile->findNext( $this->false_tokens, $start, ( $end + 1 ), true ); + // If no non-false tokens are found, we are good. + if ( false === $has_non_false ) { + return true; + } + + $code_string = ''; + for ( $i = $start; $i <= $end; $i++ ) { + if ( isset( $this->safe_tokens[ $this->tokens[ $i ]['code'] ] ) === false ) { + // Function call/variable or other token which makes it neigh impossible + // to determine whether the actual value would evaluate to false. + return false; + } + + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + continue; + } + + // Make sure that PHP 7.4 numeric literals and PHP 8.1 explicit octals don't cause problems. + if ( \T_LNUMBER === $this->tokens[ $i ]['code'] || \T_DNUMBER === $this->tokens[ $i ]['code'] ) { + $number_info = Numbers::getCompleteNumber( $this->phpcsFile, $i ); + $code_string .= $number_info['decimal']; + $i = $number_info['last_token']; + continue; + } + + $code_string .= $this->tokens[ $i ]['content']; + } + + if ( '' === $code_string ) { + return false; + } + + // Evaluate the argument to figure out the outcome is false or not. + // phpcs:ignore Squiz.PHP.Eval -- No harm here. + return ( false === eval( "return (bool) $code_string;" ) ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php new file mode 100644 index 00000000..c7ed63c3 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php @@ -0,0 +1,108 @@ +tokens[ $stackPtr ]['content']; + if ( \T_INLINE_HTML !== $this->tokens[ $stackPtr ]['code'] ) { + try { + $end_ptr = TextStrings::getEndOfCompleteTextString( $this->phpcsFile, $stackPtr ); + $content = TextStrings::getCompleteTextString( $this->phpcsFile, $stackPtr ); + } catch ( RuntimeException $e ) { + // Parse error/live coding. + return; + } + } + + if ( preg_match_all( '# rel=\\\\?[\'"]?stylesheet\\\\?[\'"]?#', $content, $matches, \PREG_OFFSET_CAPTURE ) > 0 ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'Stylesheets must be registered/enqueued via wp_enqueue_style()', + $this->find_token_in_multiline_string( $stackPtr, $content, $match[1] ), + 'NonEnqueuedStylesheet' + ); + } + } + + if ( preg_match_all( '#]*(?<=src=)#', $content, $matches, \PREG_OFFSET_CAPTURE ) > 0 ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'Scripts must be registered/enqueued via wp_enqueue_script()', + $this->find_token_in_multiline_string( $stackPtr, $content, $match[1] ), + 'NonEnqueuedScript' + ); + } + } + + return ( $end_ptr + 1 ); + } + + /** + * Find the exact token on which the error should be reported for multi-line strings. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $content The complete, potentially multi-line, text string. + * @param int $match_offset The offset within the content at which the match was found. + * + * @return int The stack pointer to the token containing the start of the match. + */ + private function find_token_in_multiline_string( $stackPtr, $content, $match_offset ) { + $newline_count = 0; + if ( $match_offset > 0 ) { + $newline_count = substr_count( $content, "\n", 0, $match_offset ); + } + + // Account for heredoc/nowdoc text starting at the token *after* the opener. + if ( isset( Tokens::$heredocTokens[ $this->tokens[ $stackPtr ]['code'] ] ) === true ) { + ++$newline_count; + } + + return ( $stackPtr + $newline_count ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php new file mode 100644 index 00000000..038aba6a --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php @@ -0,0 +1,436 @@ + Key is variable name, value irrelevant. + */ + protected $override_allowed = array( + 'content_width' => true, + 'wp_cockneyreplace' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.3.0 + * @since 1.1.0 Added class tokens for improved test classes skipping. + * + * @return array + */ + public function register() { + $targets = array( + \T_GLOBAL, + \T_VARIABLE, + ); + $targets += Collections::listOpenTokensBC(); + + // Only used to skip over test classes. + $targets += Tokens::$ooScopeTokens; + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.3.0 + * @since 1.1.0 Split the token specific logic off into separate methods. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + $token = $this->tokens[ $stackPtr ]; + + // Ignore variable overrides in test classes. + if ( isset( Tokens::$ooScopeTokens[ $token['code'] ] ) ) { + + if ( true === $this->is_test_class( $this->phpcsFile, $stackPtr ) + && $token['scope_condition'] === $stackPtr + && isset( $token['scope_closer'] ) + ) { + // Skip forward to end of test class. + return $token['scope_closer']; + } + + // Otherwise ignore the tokens as they were only registered to enable skipping over test classes. + return; + } + + /* + * Examine variables within a function scope based on a `global` statement in the + * function. + * Examine variables not within a function scope, but within a list construct, based + * on that. + * Examine variables not within a function scope and access to the `$GLOBALS` + * variable based on the variable token. + * + * Note: No special handling here for code found within PHP 7.4+ arrow functions. + * Arrow functions are "open", i.e. they have by value access to variables in the + * surrounding scope, but they cannot modify the value. + * Additionally, as they can only have one statement, a `global` statement _within_ + * an arrow function declaration will lead to a parse error as the result is + * not a returnable value. + */ + $in_function_scope = Conditions::hasCondition( $this->phpcsFile, $stackPtr, array( \T_FUNCTION, \T_CLOSURE ) ); + + if ( isset( Collections::listOpenTokensBC()[ $token['code'] ] ) + && false === $in_function_scope + && false === $this->treat_files_as_scoped + ) { + return $this->process_list_assignment( $stackPtr ); + } elseif ( \T_VARIABLE === $token['code'] + && ( '$GLOBALS' === $token['content'] + || ( false === $in_function_scope && false === $this->treat_files_as_scoped ) ) + ) { + return $this->process_variable_assignment( $stackPtr ); + } elseif ( \T_GLOBAL === $token['code'] + && ( true === $in_function_scope || true === $this->treat_files_as_scoped ) + ) { + return $this->process_global_statement( $stackPtr, $in_function_scope ); + } + } + + /** + * Check that global variables declared via a list construct are prefixed. + * + * {@internal No need to take special measures for nested lists. Nested or not, + * each list part can only contain one variable being written to.} + * + * @since 2.2.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_list_assignment( $stackPtr ) { + $list_open_close = Lists::getOpenClose( $this->phpcsFile, $stackPtr ); + if ( false === $list_open_close ) { + // Short array, not short list. + return; + } + + $var_pointers = ListHelper::get_list_variables( $this->phpcsFile, $stackPtr ); + foreach ( $var_pointers as $ptr ) { + $this->process_variable_assignment( $ptr, true ); + } + + // No need to re-examine these variables. + return $list_open_close['closer']; + } + + /** + * Check that defined global variables are prefixed. + * + * @since 1.1.0 Logic was previously contained in the process_token() method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param bool $in_list Whether or not this is a variable in a list assignment. + * Defaults to false. + * + * @return void + */ + protected function process_variable_assignment( $stackPtr, $in_list = false ) { + + $token = $this->tokens[ $stackPtr ]; + $var_name = substr( $token['content'], 1 ); // Strip the dollar sign. + $data = array(); + + // Determine the variable name for `$GLOBALS['array_key']`. + if ( 'GLOBALS' === $var_name ) { + $bracketPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + + if ( false === $bracketPtr + || \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $bracketPtr ]['code'] + || ! isset( $this->tokens[ $bracketPtr ]['bracket_closer'] ) + ) { + return; + } + + // Retrieve the array key and avoid getting tripped up by some simple obfuscation. + $var_name = ''; + $start = ( $bracketPtr + 1 ); + for ( $ptr = $start; $ptr < $this->tokens[ $bracketPtr ]['bracket_closer']; $ptr++ ) { + /* + * If the globals array key contains a variable, constant, function call + * or interpolated variable, bow out. + */ + if ( \T_VARIABLE === $this->tokens[ $ptr ]['code'] + || \T_STRING === $this->tokens[ $ptr ]['code'] + || \T_DOUBLE_QUOTED_STRING === $this->tokens[ $ptr ]['code'] + ) { + return; + } + + if ( \T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $ptr ]['code'] ) { + $var_name .= TextStrings::stripQuotes( $this->tokens[ $ptr ]['content'] ); + } + } + + if ( '' === $var_name ) { + // Shouldn't happen, but just in case. + return; + } + + // Set up the data for the error message. + $data[] = '$GLOBALS[\'' . $var_name . '\']'; + } + + /* + * Is this one of the WP global variables ? + */ + if ( WPGlobalVariablesHelper::is_wp_global( $var_name ) === false ) { + return; + } + + /* + * Is this one of the WP global variables which are allowed to be overwritten ? + */ + if ( isset( $this->override_allowed[ $var_name ] ) === true ) { + return; + } + + /* + * Check if the variable value is being changed. + */ + if ( false === $in_list + && false === VariableHelper::is_assignment( $this->phpcsFile, $stackPtr ) + && Context::inForeachCondition( $this->phpcsFile, $stackPtr ) !== 'afterAs' + ) { + return; + } + + /* + * Function parameters with the same name as a WP global variable are fine, + * including when they are being assigned a default value. + */ + if ( false === $in_list ) { + $functionPtr = Parentheses::getLastOwner( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( false !== $functionPtr ) { + return; + } + + unset( $functionPtr ); + } + + /* + * Class property declarations with the same name as WP global variables are fine. + */ + if ( false === $in_list && true === Scopes::isOOProperty( $this->phpcsFile, $stackPtr ) ) { + return; + } + + // Still here ? In that case, the WP global variable is being tampered with. + $this->add_error( $stackPtr, $data ); + } + + /** + * Check that global variables imported into a function scope using a global statement + * are not being overruled. + * + * @since 1.1.0 Logic was previously contained in the process_token() method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param bool $in_function_scope Whether the global statement is within a scoped function/closure. + * + * @return void + */ + protected function process_global_statement( $stackPtr, $in_function_scope ) { + /* + * Collect the variables to watch for. + */ + $search = array(); + $ptr = ( $stackPtr + 1 ); + $end_of_statement = $this->phpcsFile->findNext( array( \T_SEMICOLON, \T_CLOSE_TAG ), $ptr ); + + while ( isset( $this->tokens[ $ptr ] ) && $ptr < $end_of_statement ) { + if ( \T_VARIABLE === $this->tokens[ $ptr ]['code'] ) { + $var_name = substr( $this->tokens[ $ptr ]['content'], 1 ); + if ( WPGlobalVariablesHelper::is_wp_global( $var_name ) + && isset( $this->override_allowed[ $var_name ] ) === false + ) { + $search[ $this->tokens[ $ptr ]['content'] ] = true; + } + } + + ++$ptr; + } + + if ( empty( $search ) ) { + return; + } + + /* + * Search for assignments to the imported global variables within the relevant scope. + */ + $start = $ptr; + if ( true === $in_function_scope ) { + $functionPtr = Conditions::getLastCondition( $this->phpcsFile, $stackPtr, Collections::functionDeclarationTokens() ); + if ( isset( $this->tokens[ $functionPtr ]['scope_closer'] ) === false ) { + // Live coding or parse error. + return; + } + $end = $this->tokens[ $functionPtr ]['scope_closer']; + } else { + // Global statement in the global namespace in a file which is being treated as scoped. + $end = $this->phpcsFile->numTokens; + } + + for ( $ptr = $start; $ptr < $end; $ptr++ ) { + + // Skip over nested functions, classes and the likes. + if ( isset( Collections::closedScopes()[ $this->tokens[ $ptr ]['code'] ] ) ) { + if ( ! isset( $this->tokens[ $ptr ]['scope_closer'] ) ) { + // Live coding or parse error. + break; + } + + $ptr = $this->tokens[ $ptr ]['scope_closer']; + continue; + } + + // Make sure to recognize assignments to variables in a list construct. + if ( isset( Collections::listOpenTokensBC()[ $this->tokens[ $ptr ]['code'] ] ) ) { + $list_open_close = Lists::getOpenClose( $this->phpcsFile, $ptr ); + + if ( false === $list_open_close ) { + // Short array, not short list. + continue; + } + + $var_pointers = ListHelper::get_list_variables( $this->phpcsFile, $ptr ); + foreach ( $var_pointers as $ptr ) { + $var_name = $this->tokens[ $ptr ]['content']; + if ( '$GLOBALS' === $var_name ) { + $var_name = '$' . TextStrings::stripQuotes( VariableHelper::get_array_access_key( $this->phpcsFile, $ptr ) ); + } + + if ( isset( $search[ $var_name ] ) ) { + $this->process_variable_assignment( $ptr, true ); + } + } + + // No need to re-examine these variables. + $ptr = $list_open_close['closer']; + continue; + } + + if ( \T_VARIABLE !== $this->tokens[ $ptr ]['code'] ) { + continue; + } + + if ( isset( $search[ $this->tokens[ $ptr ]['content'] ] ) === false ) { + // Not one of the variables we're interested in. + continue; + } + + // Don't throw false positives for static class properties. + if ( ContextHelper::has_object_operator_before( $this->phpcsFile, $ptr ) === true ) { + continue; + } + + if ( true === VariableHelper::is_assignment( $this->phpcsFile, $ptr ) ) { + $this->add_error( $ptr ); + continue; + } + + // Check if this is a variable assignment within a `foreach()` declaration. + if ( Context::inForeachCondition( $this->phpcsFile, $ptr ) === 'afterAs' ) { + $this->add_error( $ptr ); + } + } + } + + /** + * Add the error. + * + * @since 1.1.0 + * + * @param int $stackPtr The position of the token to throw the error for. + * @param array $data Optional. Array containing one entry holding the + * name of the variable being overruled. + * Defaults to the 'content' of the $stackPtr token. + * + * @return void + */ + protected function add_error( $stackPtr, $data = array() ) { + if ( empty( $data ) ) { + $data[] = $this->tokens[ $stackPtr ]['content']; + } + + $this->phpcsFile->addError( + 'Overriding WordPress globals is prohibited. Found assignment to %s', + $stackPtr, + 'Prohibited', + $data + ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/I18nSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/I18nSniff.php new file mode 100644 index 00000000..ab857bec --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/I18nSniff.php @@ -0,0 +1,977 @@ + Key is function name, value is the function type. + */ + protected $i18n_functions = array( + 'translate' => 'simple', + '__' => 'simple', + 'esc_attr__' => 'simple', + 'esc_html__' => 'simple', + '_e' => 'simple', + 'esc_attr_e' => 'simple', + 'esc_html_e' => 'simple', + 'translate_with_gettext_context' => 'context', + '_x' => 'context', + '_ex' => 'context', + 'esc_attr_x' => 'context', + 'esc_html_x' => 'context', + '_n' => 'number', + '_nx' => 'number_context', + '_n_noop' => 'noopnumber', + '_nx_noop' => 'noopnumber_context', + ); + + /** + * Whether or not the `default` text domain is one of the allowed text domains. + * + * @since 0.14.0 + * + * @var bool + */ + private $text_domain_contains_default = false; + + /** + * Whether or not the `default` text domain is the only allowed text domain. + * + * @since 0.14.0 + * + * @var bool + */ + private $text_domain_is_default = false; + + /** + * Parameter specifications for the functions in each group. + * + * {@internal Even when not all parameters will be examined, the parameter list should still + * be complete in the below array to allow for a correct "total parameters" calculation.} + * + * @since 3.0.0 + * + * @var array Array of the parameter positions and names. + */ + private $parameter_specs = array( + 'simple' => array( + 1 => 'text', + 2 => 'domain', + ), + 'context' => array( + 1 => 'text', + 2 => 'context', + 3 => 'domain', + ), + 'number' => array( + 1 => 'single', + 2 => 'plural', + 3 => 'number', + 4 => 'domain', + ), + 'number_context' => array( + 1 => 'single', + 2 => 'plural', + 3 => 'number', + 4 => 'context', + 5 => 'domain', + ), + 'noopnumber' => array( + 1 => 'singular', + 2 => 'plural', + 3 => 'domain', + ), + 'noopnumber_context' => array( + 1 => 'singular', + 2 => 'plural', + 3 => 'context', + 4 => 'domain', + ), + ); + + /** + * Groups of functions to restrict. + * + * Example: groups => array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'i18n' => array( + 'functions' => array_keys( $this->i18n_functions ), + ), + 'typos' => array( + 'functions' => array( + '_', + ), + ), + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 Defers to the abstractFunctionRestriction sniff for determining + * whether something is a function call. The logic after that has + * been split off to the `process_matched_token()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + // Reset defaults. + $this->text_domain_contains_default = false; + $this->text_domain_is_default = false; + + // Allow overruling the text_domain set in a ruleset via the command line. + $cl_text_domain = Helper::getConfigData( 'text_domain' ); + if ( ! empty( $cl_text_domain ) ) { + $cl_text_domain = trim( $cl_text_domain ); + if ( '' !== $cl_text_domain ) { + $this->text_domain = array_filter( array_map( 'trim', explode( ',', $cl_text_domain ) ) ); + } + } + + $this->text_domain = RulesetPropertyHelper::merge_custom_array( $this->text_domain, array(), false ); + + if ( ! empty( $this->text_domain ) ) { + if ( \in_array( 'default', $this->text_domain, true ) ) { + $this->text_domain_contains_default = true; + if ( \count( $this->text_domain ) === 1 ) { + $this->text_domain_is_default = true; + } + } + } + + // Prevent exclusion of the i18n group. + $this->exclude = array(); + + parent::process_token( $stackPtr ); + } + + /** + * Process a matched token. + * + * @since 1.0.0 Logic split off from the `process_token()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $func_open_paren_token = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( ! isset( $this->tokens[ $func_open_paren_token ]['parenthesis_closer'] ) ) { + // Live coding, parse error or not a function call. + return; + } + + if ( 'typos' === $group_name && '_' === $matched_content ) { + $this->phpcsFile->addError( + 'Found single-underscore "_()" function when double-underscore expected.', + $stackPtr, + 'SingleUnderscoreGetTextFunction' + ); + return; + } + + if ( 'translate' === $matched_content || 'translate_with_gettext_context' === $matched_content ) { + $this->phpcsFile->addWarning( + 'Use of the "%s()" function is reserved for low-level API usage.', + $stackPtr, + 'LowLevelTranslationFunction', + array( $matched_content ) + ); + } + + parent::process_matched_token( $stackPtr, $group_name, $matched_content ); + } + + /** + * Process the function if no parameters were found. + * + * @since 3.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * + * @return void + */ + public function process_no_parameters( $stackPtr, $group_name, $matched_content ) { + $function_param_specs = $this->parameter_specs[ $this->i18n_functions[ $matched_content ] ]; + + foreach ( $function_param_specs as $param_name ) { + $error_code = MessageHelper::stringToErrorcode( 'MissingArg' . ucfirst( $param_name ) ); + $this->phpcsFile->addError( + 'Missing $%s parameter in function call to %s().', + $stackPtr, + $error_code, + array( $param_name, $matched_content ) + ); + } + } + + /** + * Process the parameters of a matched function. + * + * @since 3.0.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + /* + * Retrieve the individual parameters from the array in a way that we know which is which. + */ + $parameter_details = array(); + + $function_param_specs = $this->parameter_specs[ $this->i18n_functions[ $matched_content ] ]; + $expected_args = count( $function_param_specs ); + + foreach ( $function_param_specs as $position => $name ) { + if ( 'number' === $name ) { + // This sniff does not examine the $number parameter. + continue; + } + + $parameter_details[ $name ] = PassedParameters::getParameterFromStack( $parameters, $position, $name ); + } + + /* + * Examine the individual parameters. + */ + $this->check_argument_count( $stackPtr, $matched_content, $parameters, $expected_args ); + + foreach ( $parameter_details as $param_name => $param_info ) { + $is_string_literal = $this->check_argument_is_string_literal( $stackPtr, $matched_content, $param_name, $param_info ); + + /* + * If the parameter exists, remember whether the argument was a valid string literal. + * This is used in a few places to determine whether the checks which examine a text string should run. + */ + if ( false !== $param_info ) { + $parameter_details[ $param_name ]['is_string_literal'] = $is_string_literal; + } + + if ( false === $is_string_literal ) { + continue; + } + + if ( 'domain' === $param_name ) { + $this->check_textdomain_matches( $matched_content, $param_name, $param_info ); + } + + if ( \in_array( $param_name, array( 'text', 'single', 'singular', 'plural' ), true ) ) { + $this->check_placeholders_in_string( $matched_content, $param_name, $param_info ); + $has_content = $this->check_string_has_translatable_content( $matched_content, $param_name, $param_info ); + if ( true === $has_content ) { + $this->check_string_has_no_html_wrapper( $matched_content, $param_name, $param_info ); + } + } + } + + /* + * For _n*() calls, compare the singular and plural strings. + * + * If either of the arguments is missing, empty or has more than 1 token, skip out. + * An error for that will already have been reported via the `check_argument_is_string_literal()` method. + */ + $single_details = null; + if ( isset( $parameter_details['single'] ) ) { + $single_details = $parameter_details['single']; + } elseif ( isset( $parameter_details['singular'] ) ) { + $single_details = $parameter_details['singular']; + } + + if ( isset( $single_details, $parameter_details['plural'] ) + && false !== $single_details + && false !== $parameter_details['plural'] + && true === $single_details['is_string_literal'] + && true === $parameter_details['plural']['is_string_literal'] + ) { + $this->compare_single_and_plural_arguments( $stackPtr, $single_details, $parameter_details['plural'] ); + } + + /* + * Check if a translators comments is necessary and if so, if it exists. + */ + $this->check_for_translator_comment( $stackPtr, $matched_content, $parameter_details ); + } + + /** + * Verify that there are no superfluous function arguments. + * + * @since 3.0.0 Check moved from the `process_matched_token()` method to this method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters The parameters array. + * @param int $expected_count The expected number of passed arguments. + * + * @return void + */ + private function check_argument_count( $stackPtr, $matched_content, $parameters, $expected_count ) { + $actual_count = count( $parameters ); + if ( $actual_count > $expected_count ) { + $this->phpcsFile->addError( + 'Too many parameters passed to function "%s()". Expected: %s parameters, received: %s', + $stackPtr, + 'TooManyFunctionArgs', + array( $matched_content, $expected_count, $actual_count ) + ); + } + } + + /** + * Check if an arbitrary function call parameter is a text string literal suitable for use in the translation functions. + * + * Will also check and warn about missing parameters. + * + * @since 3.0.0 Most of the logic in this method used to be contained in the, now removed, `check_argument_tokens()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParemeters class. + * + * @return bool Whether or not the argument is a string literal. + */ + private function check_argument_is_string_literal( $stackPtr, $matched_content, $param_name, $param_info ) { + /* + * Check if the parameter was supplied. + */ + if ( false === $param_info || '' === $param_info['clean'] ) { + $error_code = MessageHelper::stringToErrorcode( 'MissingArg' . ucfirst( $param_name ) ); + + /* + * Special case the text domain parameter, which is allowed to be "missing" + * when set to `default` (= WP Core translation). + */ + if ( 'domain' === $param_name ) { + if ( empty( $this->text_domain ) ) { + // If no text domain is passed, presume WP Core. + return false; + } + + if ( true === $this->text_domain_is_default ) { + return false; + } + + if ( true === $this->text_domain_contains_default ) { + $this->phpcsFile->addWarning( + 'Missing $%s parameter in function call to %s(). If this text string is supposed to use a WP Core translation, use the "default" text domain.', + $stackPtr, + $error_code . 'Default', + array( $param_name, $matched_content ) + ); + return false; + } + } + + $this->phpcsFile->addError( + 'Missing $%s parameter in function call to %s().', + $stackPtr, + $error_code, + array( $param_name, $matched_content ) + ); + + return false; + } + + /* + * Check if the parameter consists of one singular text string literal. + * Heredoc/nowdocs not allowed. Multi-line single/double quoted strings are allowed. + */ + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + for ( $i = $first_non_empty; $i <= $param_info['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) === false ) { + $error_code = MessageHelper::stringToErrorcode( 'NonSingularStringLiteral' . ucfirst( $param_name ) ); + $this->phpcsFile->addError( + 'The $%s parameter must be a single text string literal. Found: %s', + $first_non_empty, + $error_code, + array( $param_name, $param_info['clean'] ) + ); + return false; + } + } + + /* + * Make sure the text string does not contain any interpolated variable. + */ + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $first_non_empty ]['code'] ) { + $error_code = MessageHelper::stringToErrorcode( 'InterpolatedVariable' . ucfirst( $param_name ) ); + + $interpolated_variables = TextStrings::getEmbeds( $param_info['clean'] ); + foreach ( $interpolated_variables as $interpolated_variable ) { + $this->phpcsFile->addError( + 'The $%s parameter must not contain interpolated variables or expressions. Found: %s', + $first_non_empty, + $error_code, + array( $param_name, $interpolated_variable ) + ); + } + + if ( ! empty( $interpolated_variables ) ) { + return false; + } + } + + return true; + } + + /** + * Check the correct text domain is being used. + * + * @since 3.0.0 The logic in this method used to be contained in the, now removed, `check_argument_tokens()` method. + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParemeters class. + * + * @return void + */ + private function check_textdomain_matches( $matched_content, $param_name, $param_info ) { + $stripped_content = TextStrings::stripQuotes( $param_info['clean'] ); + + if ( empty( $this->text_domain ) && '' === $stripped_content ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + $this->phpcsFile->addError( + 'The passed $domain should never be an empty string. Either pass a text domain or remove the parameter.', + $first_non_empty, + 'EmptyTextDomain' + ); + } + + if ( empty( $this->text_domain ) ) { + // Nothing more to do, the other checks all depend on a text domain being known. + return; + } + + if ( ! \in_array( $stripped_content, $this->text_domain, true ) ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + $this->phpcsFile->addError( + 'Mismatched text domain. Expected \'%s\' but got %s.', + $first_non_empty, + 'TextDomainMismatch', + array( implode( "' or '", $this->text_domain ), $param_info['clean'] ) + ); + return; + } + + if ( true === $this->text_domain_is_default && 'default' === $stripped_content ) { + $fixable = false; + $error = 'No need to supply the text domain in function call to %s() when the only accepted text domain is "default".'; + $error_code = 'SuperfluousDefaultTextDomain'; + $data = array( $matched_content ); + + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + // Prevent removing comments when auto-fixing. + $remove_from = ( $param_info['start'] - 1 ); + $remove_to = $first_non_empty; + + if ( isset( $param_info['name_token'] ) ) { + $remove_from = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $param_info['name_token'] - 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $remove_from ]['code'] ) { + ++$remove_from; // Don't remove the open parenthesis. + + /* + * Named param as first param in the function call, if we fix this, we need to + * remove the comma _after_ the parameter as well to prevent creating a parse error. + */ + $remove_to = $param_info['end']; + if ( \T_COMMA === $this->tokens[ ( $param_info['end'] + 1 ) ]['code'] ) { + ++$remove_to; // Include the comma. + } + } + } + + // Now, make sure there are no comments in the tokens we want to remove. + if ( $this->phpcsFile->findNext( Tokens::$commentTokens, $remove_from, ( $remove_to + 1 ) ) === false ) { + $fixable = true; + } + + if ( false === $fixable ) { + $this->phpcsFile->addWarning( $error, $first_non_empty, $error_code, $data ); + return; + } + + $fix = $this->phpcsFile->addFixableWarning( $error, $first_non_empty, $error_code, $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = $remove_from; $i <= $remove_to; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + } + } + } + + /** + * Check the placeholders used in translatable text for common problems. + * + * @since 3.0.0 The logic in this method used to be contained in the, now removed, `check_text()` method. + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParemeters class. + * + * @return void + */ + private function check_placeholders_in_string( $matched_content, $param_name, $param_info ) { + $content = $param_info['clean']; + + // UnorderedPlaceholders: Check for multiple unordered placeholders. + $unordered_matches_count = preg_match_all( self::UNORDERED_SPRINTF_PLACEHOLDER_REGEX, $content, $unordered_matches ); + $unordered_matches = $unordered_matches[0]; + $all_matches_count = preg_match_all( self::SPRINTF_PLACEHOLDER_REGEX, $content, $all_matches ); + + if ( $unordered_matches_count > 0 + && $unordered_matches_count !== $all_matches_count + && $all_matches_count > 1 + ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + $error_code = MessageHelper::stringToErrorcode( 'MixedOrderedPlaceholders' . ucfirst( $param_name ) ); + + $this->phpcsFile->addError( + 'Multiple placeholders in translatable strings should be ordered. Mix of ordered and non-ordered placeholders found. Found: "%s" in %s.', + $first_non_empty, + $error_code, + array( implode( ', ', $all_matches[0] ), $param_info['clean'] ) + ); + return; + } + + if ( $unordered_matches_count >= 2 ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + $error_code = MessageHelper::stringToErrorcode( 'UnorderedPlaceholders' . ucfirst( $param_name ) ); + + $suggestions = array(); + $replace_regexes = array(); + $replacements = array(); + for ( $i = 0; $i < $unordered_matches_count; $i++ ) { + $to_insert = ( $i + 1 ); + $to_insert .= ( '"' !== $content[0] ) ? '$' : '\$'; + $suggestions[ $i ] = substr_replace( $unordered_matches[ $i ], $to_insert, 1, 0 ); + + // Prepare the strings for use in a regex. + $replace_regexes[ $i ] = '`\Q' . $unordered_matches[ $i ] . '\E`'; + // Note: the initial \\ is a literal \, the four \ in the replacement translate also to a literal \. + $replacements[ $i ] = str_replace( '\\', '\\\\', $suggestions[ $i ] ); + // Note: the $ needs escaping to prevent numeric sequences after the $ being interpreted as match replacements. + $replacements[ $i ] = str_replace( '$', '\\$', $replacements[ $i ] ); + } + + $fix = $this->phpcsFile->addFixableError( + 'Multiple placeholders in translatable strings should be ordered. Expected "%s", but got "%s" in %s.', + $first_non_empty, + $error_code, + array( implode( ', ', $suggestions ), implode( ', ', $unordered_matches ), $param_info['clean'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + $fixed_str = preg_replace( $replace_regexes, $replacements, $content, 1 ); + + $this->phpcsFile->fixer->replaceToken( $first_non_empty, $fixed_str ); + + $i = ( $first_non_empty + 1 ); + while ( $i <= $param_info['end'] && isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + ++$i; + } + + $this->phpcsFile->fixer->endChangeset(); + } + } + } + + /** + * Check if a parameter which is supposed to hold translatable text actually has translatable text. + * + * @since 3.0.0 The logic in this method used to be contained in the, now removed, `check_text()` method. + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParemeters class. + * + * @return bool Whether or not the text string has translatable content. + */ + private function check_string_has_translatable_content( $matched_content, $param_name, $param_info ) { + // Strip placeholders and surrounding quotes. + $content_without_quotes = trim( TextStrings::stripQuotes( $param_info['clean'] ) ); + $non_placeholder_content = preg_replace( self::SPRINTF_PLACEHOLDER_REGEX, '', $content_without_quotes ); + + if ( '' === $non_placeholder_content ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + $this->phpcsFile->addError( + 'The $%s text string should have translatable content. Found: %s', + $first_non_empty, + 'NoEmptyStrings', + array( $param_name, $param_info['clean'] ) + ); + return false; + } + + return true; + } + + /** + * Ensure that a translatable text string is not wrapped in HTML code. + * + * If the text is wrapped in HTML, the HTML should be moved out of the translatable text string. + * + * @since 3.0.0 The logic in this method used to be contained in the, now removed, `check_text()` method. + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParemeters class. + * + * @return void + */ + private function check_string_has_no_html_wrapper( $matched_content, $param_name, $param_info ) { + // Strip surrounding quotes. + $content_without_quotes = trim( TextStrings::stripQuotes( $param_info['clean'] ) ); + + $reader = new XMLReader(); + $reader->XML( $content_without_quotes, 'UTF-8', \LIBXML_NOERROR | \LIBXML_ERR_NONE | \LIBXML_NOWARNING ); + + // Is the first node an HTML element? + if ( ! $reader->read() || XMLReader::ELEMENT !== $reader->nodeType ) { + return; + } + + // If the opening HTML element includes placeholders in its attributes, we don't warn. + // E.g. ''. + $i = 0; + while ( $attr = $reader->getAttributeNo( $i ) ) { + if ( preg_match( self::SPRINTF_PLACEHOLDER_REGEX, $attr ) === 1 ) { + return; + } + + ++$i; + } + + // We don't flag strings wrapped in `...`, as the link target might actually need localization. + if ( 'a' === $reader->name && $reader->getAttribute( 'href' ) ) { + return; + } + + // Does the entire string only consist of this HTML node? + if ( $reader->readOuterXml() === $content_without_quotes ) { + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + $this->phpcsFile->addWarning( + 'Translatable string should not be wrapped in HTML. Found: %s', + $first_non_empty, + 'NoHtmlWrappedStrings', + array( $param_info['clean'] ) + ); + } + } + + /** + * Check for inconsistencies in the placeholders between single and plural form of the translatable text string. + * + * @since 3.0.0 - The parameter names and expected format for the $param_info_single + * and the $param_info_plural parameters has changed. + * - The method visibility has been changed from `protected` to `private`. + * + * @param int $stackPtr The position of the function call token in the stack. + * @param array $param_info_single Parameter info array for the `$single` parameter, + * as received from the PassedParemeters class. + * @param array $param_info_plural Parameter info array for the `$plural` parameter, + * as received from the PassedParemeters class. + * + * @return void + */ + private function compare_single_and_plural_arguments( $stackPtr, $param_info_single, $param_info_plural ) { + $single_content = $param_info_single['clean']; + $plural_content = $param_info_plural['clean']; + + preg_match_all( self::SPRINTF_PLACEHOLDER_REGEX, $single_content, $single_placeholders ); + $single_placeholders = $single_placeholders[0]; + + preg_match_all( self::SPRINTF_PLACEHOLDER_REGEX, $plural_content, $plural_placeholders ); + $plural_placeholders = $plural_placeholders[0]; + + // English conflates "singular" with "only one", described in the codex: + // https://codex.wordpress.org/I18n_for_WordPress_Developers#Plurals . + if ( \count( $single_placeholders ) < \count( $plural_placeholders ) ) { + $error_string = 'Missing singular placeholder, needed for some languages. See https://codex.wordpress.org/I18n_for_WordPress_Developers#Plurals'; + $first_non_empty_single = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info_single['start'], ( $param_info_single['end'] + 1 ), true ); + + $this->phpcsFile->addError( $error_string, $first_non_empty_single, 'MissingSingularPlaceholder' ); + return; + } + + // Reordering is fine, but mismatched placeholders is probably wrong. + sort( $single_placeholders, \SORT_NATURAL ); + sort( $plural_placeholders, \SORT_NATURAL ); + + if ( $single_placeholders !== $plural_placeholders ) { + $this->phpcsFile->addWarning( 'Mismatched placeholders is probably an error', $stackPtr, 'MismatchedPlaceholders' ); + } + } + + /** + * Check for the presence of a translators comment if one of the text strings contains a placeholder. + * + * @since 3.0.0 - The parameter names and expected format for the $parameters parameter has changed. + * - The method visibility has been changed from `protected` to `private`. + * + * @param int $stackPtr The position of the gettext call token in the stack. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void + */ + private function check_for_translator_comment( $stackPtr, $matched_content, $parameters ) { + $needs_translators_comment = false; + + foreach ( $parameters as $param_name => $param_info ) { + if ( false === \in_array( $param_name, array( 'text', 'single', 'singular', 'plural' ), true ) ) { + continue; + } + + if ( false === $param_info || false === $param_info['is_string_literal'] ) { + continue; + } + + if ( preg_match( self::SPRINTF_PLACEHOLDER_REGEX, $param_info['clean'], $placeholders ) === 1 ) { + $needs_translators_comment = true; + break; + } + } + + if ( false === $needs_translators_comment ) { + // No text string with placeholders found, no translation comment needed. + return; + } + + $previous_comment = $this->phpcsFile->findPrevious( Tokens::$commentTokens, ( $stackPtr - 1 ) ); + + if ( false !== $previous_comment ) { + /* + * Check that the comment is either on the line before the gettext call or + * if it's not, that there is only whitespace between. + */ + $correctly_placed = false; + + if ( ( $this->tokens[ $previous_comment ]['line'] + 1 ) === $this->tokens[ $stackPtr ]['line'] ) { + $correctly_placed = true; + } else { + $next_non_whitespace = $this->phpcsFile->findNext( \T_WHITESPACE, ( $previous_comment + 1 ), $stackPtr, true ); + if ( false === $next_non_whitespace || $this->tokens[ $next_non_whitespace ]['line'] === $this->tokens[ $stackPtr ]['line'] ) { + // No non-whitespace found or next non-whitespace is on same line as gettext call. + $correctly_placed = true; + } + unset( $next_non_whitespace ); + } + + /* + * Check that the comment starts with 'translators:'. + */ + if ( true === $correctly_placed ) { + if ( \T_COMMENT === $this->tokens[ $previous_comment ]['code'] ) { + $comment_text = trim( $this->tokens[ $previous_comment ]['content'] ); + + // If it's multi-line /* */ comment, collect all the parts. + if ( '*/' === substr( $comment_text, -2 ) && '/*' !== substr( $comment_text, 0, 2 ) ) { + for ( $i = ( $previous_comment - 1 ); 0 <= $i; $i-- ) { + if ( \T_COMMENT !== $this->tokens[ $i ]['code'] ) { + break; + } + + $comment_text = trim( $this->tokens[ $i ]['content'] ) . $comment_text; + } + } + + if ( true === $this->is_translators_comment( $comment_text ) ) { + // Comment is ok. + return; + } + } + + if ( \T_DOC_COMMENT_CLOSE_TAG === $this->tokens[ $previous_comment ]['code'] ) { + // If it's docblock comment (wrong style) make sure that it's a translators comment. + if ( isset( $this->tokens[ $previous_comment ]['comment_opener'] ) ) { + $db_start = $this->tokens[ $previous_comment ]['comment_opener']; + } else { + $db_start = $this->phpcsFile->findPrevious( \T_DOC_COMMENT_OPEN_TAG, ( $previous_comment - 1 ) ); + } + + $db_first_text = $this->phpcsFile->findNext( \T_DOC_COMMENT_STRING, ( $db_start + 1 ), $previous_comment ); + + if ( true === $this->is_translators_comment( $this->tokens[ $db_first_text ]['content'] ) ) { + $this->phpcsFile->addWarning( + 'A "translators:" comment must be a "/* */" style comment. Docblock comments will not be picked up by the tools to generate a ".pot" file.', + $stackPtr, + 'TranslatorsCommentWrongStyle' + ); + return; + } + } + } + } + + // Found placeholders but no translators comment. + $this->phpcsFile->addWarning( + 'A function call to %s() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.', + $stackPtr, + 'MissingTranslatorsComment', + array( $matched_content ) + ); + } + + /** + * Check if a (collated) comment string starts with 'translators:'. + * + * @since 0.11.0 + * + * @param string $content Comment string content. + * + * @return bool + */ + private function is_translators_comment( $content ) { + if ( preg_match( '`^(?:(?://|/\*{1,2}) )?translators:`i', $content, $matches ) === 1 ) { + return true; + } + return false; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/PostsPerPageSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/PostsPerPageSniff.php new file mode 100644 index 00000000..a3f8ce45 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WP/PostsPerPageSniff.php @@ -0,0 +1,98 @@ + array( + 'type' => 'warning', + 'message' => 'Detected high pagination limit, `%s` is set to `%s`', + 'keys' => array( + 'posts_per_page', + 'numberposts', + ), + ), + ); + } + + /** + * Callback to process each confirmed key, to check value. + * + * @param string $key Array index / key. + * @param mixed $val Assigned value. + * @param int $line Token line. + * @param array $group Group definition. + * + * @return bool FALSE if no match, TRUE if matches. + */ + public function callback( $key, $val, $line, $group ) { + $stripped_val = TextStrings::stripQuotes( $val ); + + if ( $val !== $stripped_val ) { + // The value was a text string. For text strings, we only accept purely numeric values. + if ( preg_match( '`^[0-9]+$`', $stripped_val ) !== 1 ) { + // Not a purely numeric value, so any comparison would be a false comparison. + return false; + } + + // Purely numeric string, treat it as an integer from here on out. + $val = $stripped_val; + } + + $first_char = $val[0]; + if ( '-' === $first_char || '+' === $first_char ) { + $val = ltrim( $val, '-+' ); + } else { + $first_char = ''; + } + + $real_value = Numbers::getDecimalValue( $val ); + if ( false === $real_value ) { + // This wasn't a purely numeric value, so any comparison would be a false comparison. + return false; + } + + $val = $first_char . $real_value; + + return ( (int) $val > (int) $this->posts_per_page ); + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php new file mode 100644 index 00000000..b00303ed --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php @@ -0,0 +1,59 @@ +tokens[ ( $stackPtr - 1 ) ]['code'] + && \T_ELLIPSIS !== $this->tokens[ ( $stackPtr - 1 ) ]['code'] + ) { + $error = 'Expected a space before the type cast open parenthesis; none found'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBeforeOpenParenthesis' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->addContentBefore( $stackPtr, ' ' ); + } + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php new file mode 100644 index 00000000..11fecd64 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -0,0 +1,486 @@ + true, + \T_ELSE => true, + \T_TRY => true, + \T_FINALLY => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + \T_IF, + \T_WHILE, + \T_FOREACH, + \T_FOR, + \T_SWITCH, + \T_DO, + \T_ELSE, + \T_ELSEIF, + \T_TRY, + \T_CATCH, + \T_FINALLY, + \T_MATCH, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + if ( isset( $this->tokens[ ( $stackPtr + 1 ) ] ) && \T_WHITESPACE !== $this->tokens[ ( $stackPtr + 1 ) ]['code'] + && ! ( \T_ELSE === $this->tokens[ $stackPtr ]['code'] && \T_COLON === $this->tokens[ ( $stackPtr + 1 ) ]['code'] ) + ) { + $error = 'Space after opening control structure is required'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterStructureOpen' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); + } + } + + if ( ! isset( $this->tokens[ $stackPtr ]['scope_opener'], $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + if ( \T_WHILE !== $this->tokens[ $stackPtr ]['code'] ) { + return; + } + } else { + $scopeOpener = $this->tokens[ $stackPtr ]['scope_opener']; + $scopeCloser = $this->tokens[ $stackPtr ]['scope_closer']; + } + + // Alternative syntax. + if ( isset( $scopeOpener ) && \T_COLON === $this->tokens[ $scopeOpener ]['code'] ) { + + if ( 'required' === $this->space_before_colon ) { + + if ( \T_WHITESPACE !== $this->tokens[ ( $scopeOpener - 1 ) ]['code'] ) { + $error = 'Space between opening control structure and T_COLON is required'; + $fix = $this->phpcsFile->addFixableError( $error, $scopeOpener, 'NoSpaceBetweenStructureColon' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContentBefore( $scopeOpener, ' ' ); + } + } + } elseif ( 'forbidden' === $this->space_before_colon ) { + + if ( \T_WHITESPACE === $this->tokens[ ( $scopeOpener - 1 ) ]['code'] ) { + $error = 'Extra space between opening control structure and T_COLON found'; + $fix = $this->phpcsFile->addFixableError( $error, ( $scopeOpener - 1 ), 'SpaceBetweenStructureColon' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $scopeOpener - 1 ), '' ); + } + } + } + } + + $parenthesisOpener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + + if ( \T_COLON !== $this->tokens[ $parenthesisOpener ]['code'] + && ( $stackPtr + 1 ) === $parenthesisOpener + ) { + // Checking space between keyword and open parenthesis, i.e. `if[*](...) {}`. + $error = 'No space before opening parenthesis is prohibited'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBeforeOpenParenthesis' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); + } + } + + if ( \T_WHITESPACE === $this->tokens[ ( $stackPtr + 1 ) ]['code'] + && ' ' !== $this->tokens[ ( $stackPtr + 1 ) ]['content'] + ) { + // Checking (too much) space between keyword and open parenthesis, i.e. `if [*](...) {}`. + $error = 'Expected exactly one space before opening parenthesis; "%s" found.'; + $fix = $this->phpcsFile->addFixableError( + $error, + $stackPtr, + 'ExtraSpaceBeforeOpenParenthesis', + array( $this->tokens[ ( $stackPtr + 1 ) ]['content'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), ' ' ); + } + } + + if ( \T_CLOSE_PARENTHESIS !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['code'] ) { + if ( \T_WHITESPACE !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['code'] ) { + // Checking space directly after the open parenthesis, i.e. `if ([*]...) {}`. + $error = 'No space after opening parenthesis is prohibited'; + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterOpenParenthesis' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $parenthesisOpener, ' ' ); + } + } elseif ( ( ' ' !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] + && "\n" !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] + && "\r\n" !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] ) + && ! isset( $this->ignore_extra_space_after_open_paren[ $this->tokens[ $stackPtr ]['code'] ] ) + ) { + // Checking (too much) space directly after the open parenthesis, i.e. `if ([*]...) {}`. + $error = 'Expected exactly one space after opening parenthesis; "%s" found.'; + $fix = $this->phpcsFile->addFixableError( + $error, + $stackPtr, + 'ExtraSpaceAfterOpenParenthesis', + array( $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $parenthesisOpener + 1 ), ' ' ); + } + } + } + + if ( isset( $this->tokens[ $parenthesisOpener ]['parenthesis_closer'] ) ) { + + $parenthesisCloser = $this->tokens[ $parenthesisOpener ]['parenthesis_closer']; + + if ( \T_CLOSE_PARENTHESIS !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['code'] ) { + + // Checking space directly before the close parenthesis, i.e. `if (...[*]) {}`. + if ( \T_WHITESPACE !== $this->tokens[ ( $parenthesisCloser - 1 ) ]['code'] ) { + $error = 'No space before closing parenthesis is prohibited'; + $fix = $this->phpcsFile->addFixableError( $error, $parenthesisCloser, 'NoSpaceBeforeCloseParenthesis' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContentBefore( $parenthesisCloser, ' ' ); + } + } elseif ( ' ' !== $this->tokens[ ( $parenthesisCloser - 1 ) ]['content'] ) { + $prevNonEmpty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $parenthesisCloser - 1 ), null, true ); + if ( $this->tokens[ ( $parenthesisCloser ) ]['line'] === $this->tokens[ ( $prevNonEmpty + 1 ) ]['line'] ) { + $error = 'Expected exactly one space before closing parenthesis; "%s" found.'; + $fix = $this->phpcsFile->addFixableError( + $error, + $stackPtr, + 'ExtraSpaceBeforeCloseParenthesis', + array( $this->tokens[ ( $parenthesisCloser - 1 ) ]['content'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $parenthesisCloser - 1 ), ' ' ); + } + } + } + + if ( \T_WHITESPACE !== $this->tokens[ ( $parenthesisCloser + 1 ) ]['code'] + && ( isset( $scopeOpener ) && \T_COLON !== $this->tokens[ $scopeOpener ]['code'] ) + ) { + $error = 'Space between opening control structure and closing parenthesis is required'; + $fix = $this->phpcsFile->addFixableError( $error, $scopeOpener, 'NoSpaceAfterCloseParenthesis' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContentBefore( $scopeOpener, ' ' ); + } + } + } + + if ( isset( $this->tokens[ $parenthesisOpener ]['parenthesis_owner'] ) + && ( isset( $scopeOpener ) + && $this->tokens[ $parenthesisCloser ]['line'] !== $this->tokens[ $scopeOpener ]['line'] ) + ) { + $error = 'Opening brace should be on the same line as the declaration'; + $fix = $this->phpcsFile->addFixableError( $error, $parenthesisOpener, 'OpenBraceNotSameLine' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $i = ( $parenthesisCloser + 1 ); $i < $scopeOpener; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->addContent( $parenthesisCloser, ' ' ); + $this->phpcsFile->fixer->endChangeset(); + } + return; + + } elseif ( \T_WHITESPACE === $this->tokens[ ( $parenthesisCloser + 1 ) ]['code'] + && ' ' !== $this->tokens[ ( $parenthesisCloser + 1 ) ]['content'] + ) { + // Checking space between the close parenthesis and the open brace, i.e. `if (...) [*]{}`. + $error = 'Expected exactly one space between closing parenthesis and opening control structure; "%s" found.'; + $fix = $this->phpcsFile->addFixableError( + $error, + $stackPtr, + 'ExtraSpaceAfterCloseParenthesis', + array( $this->tokens[ ( $parenthesisCloser + 1 ) ]['content'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $parenthesisCloser + 1 ), ' ' ); + } + } + } + + if ( false !== $this->blank_line_check && isset( $scopeOpener ) ) { + $firstContent = $this->phpcsFile->findNext( \T_WHITESPACE, ( $scopeOpener + 1 ), null, true ); + + // We ignore spacing for some structures that tend to have their own rules. + $ignore = array( + \T_DOC_COMMENT_OPEN_TAG => true, + \T_CLOSE_TAG => true, + \T_COMMENT => true, + ); + $ignore += Collections::closedScopes(); + + if ( ! isset( $ignore[ $this->tokens[ $firstContent ]['code'] ] ) + && $this->tokens[ $firstContent ]['line'] > ( $this->tokens[ $scopeOpener ]['line'] + 1 ) + ) { + $gap = ( $this->tokens[ $firstContent ]['line'] - $this->tokens[ $scopeOpener ]['line'] - 1 ); + $this->phpcsFile->recordMetric( $stackPtr, 'Blank lines at start of control structure', $gap ); + + $error = 'Blank line found at start of control structure'; + $fix = $this->phpcsFile->addFixableError( $error, $scopeOpener, 'BlankLineAfterStart' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $i = ( $scopeOpener + 1 ); $i < $firstContent; $i++ ) { + if ( $this->tokens[ $i ]['line'] === $this->tokens[ $firstContent ]['line'] ) { + break; + } + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->addNewline( $scopeOpener ); + $this->phpcsFile->fixer->endChangeset(); + } + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Blank lines at start of control structure', 0 ); + } + + if ( isset( $scopeCloser ) && $firstContent !== $scopeCloser ) { + $lastContent = $this->phpcsFile->findPrevious( \T_WHITESPACE, ( $scopeCloser - 1 ), null, true ); + + $lastNonEmptyContent = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $scopeCloser - 1 ), null, true ); + + $checkToken = $lastContent; + if ( isset( $this->tokens[ $lastNonEmptyContent ]['scope_condition'] ) ) { + $checkToken = $this->tokens[ $lastNonEmptyContent ]['scope_condition']; + } + + if ( ! isset( $ignore[ $this->tokens[ $checkToken ]['code'] ] ) + && $this->tokens[ $lastContent ]['line'] <= ( $this->tokens[ $scopeCloser ]['line'] - 2 ) + ) { + $gap = ( $this->tokens[ $scopeCloser ]['line'] - $this->tokens[ $lastContent ]['line'] - 1 ); + $this->phpcsFile->recordMetric( $stackPtr, 'Blank lines at end of control structure', $gap ); + + for ( $i = ( $scopeCloser - 1 ); $i > $lastContent; $i-- ) { + if ( $this->tokens[ $i ]['line'] < $this->tokens[ $scopeCloser ]['line'] + && \T_OPEN_TAG !== $this->tokens[ $firstContent ]['code'] + ) { + // TODO: Reporting error at empty line won't highlight it in IDE. + $error = 'Blank line found at end of control structure'; + $fix = $this->phpcsFile->addFixableError( $error, $i, 'BlankLineBeforeEnd' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + for ( $j = ( $lastContent + 1 ); $j < $scopeCloser; $j++ ) { + if ( $this->tokens[ $j ]['line'] === $this->tokens[ $scopeCloser ]['line'] ) { + break; + } + $this->phpcsFile->fixer->replaceToken( $j, '' ); + } + + /* + * PHPCS annotations, like normal inline comments, are tokenized including + * the new line at the end, so don't add any extra as it would cause a fixer + * conflict. + */ + if ( \T_COMMENT !== $this->tokens[ $lastContent ]['code'] + && ! isset( Tokens::$phpcsCommentTokens[ $this->tokens[ $lastContent ]['code'] ] ) ) { + $this->phpcsFile->fixer->addNewlineBefore( $j ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + break; + } + } + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Blank lines at end of control structure', 0 ); + } + } + unset( $ignore ); + } + + if ( ! isset( $scopeCloser ) || true !== $this->blank_line_after_check ) { + return; + } + + if ( \T_MATCH === $this->tokens[ $stackPtr ]['code'] ) { + // Move the scope closer to the semicolon/comma. + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $scopeCloser + 1 ), null, true ); + if ( false !== $next + && ( \T_SEMICOLON === $this->tokens[ $next ]['code'] || \T_COMMA === $this->tokens[ $next ]['code'] ) + ) { + $scopeCloser = $next; + } + } + + // {@internal This is just for the blank line check. Only whitespace should be considered, + // not "other" empty tokens.}} + $trailingContent = $this->phpcsFile->findNext( \T_WHITESPACE, ( $scopeCloser + 1 ), null, true ); + if ( false === $trailingContent ) { + return; + } + + if ( \T_COMMENT === $this->tokens[ $trailingContent ]['code'] + || isset( Tokens::$phpcsCommentTokens[ $this->tokens[ $trailingContent ]['code'] ] ) + ) { + // Special exception for code where the comment about + // an ELSE or ELSEIF is written between the control structures. + $nextCode = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $scopeCloser + 1 ), null, true ); + + if ( \T_ELSE === $this->tokens[ $nextCode ]['code'] || \T_ELSEIF === $this->tokens[ $nextCode ]['code'] ) { + $trailingContent = $nextCode; + } + + // Move past end comments. + if ( $this->tokens[ $trailingContent ]['line'] === $this->tokens[ $scopeCloser ]['line'] ) { + if ( preg_match( '`^//[ ]?end`i', $this->tokens[ $trailingContent ]['content'], $matches ) > 0 ) { + $scopeCloser = $trailingContent; + $trailingContent = $this->phpcsFile->findNext( \T_WHITESPACE, ( $trailingContent + 1 ), null, true ); + } + } + } + + if ( \T_ELSE === $this->tokens[ $trailingContent ]['code'] && \T_IF === $this->tokens[ $stackPtr ]['code'] ) { + // IF with ELSE. + return; + } + + if ( \T_WHILE === $this->tokens[ $trailingContent ]['code'] && \T_DO === $this->tokens[ $stackPtr ]['code'] ) { + // DO with WHILE. + return; + } + + if ( \T_CATCH === $this->tokens[ $trailingContent ]['code'] && \T_TRY === $this->tokens[ $stackPtr ]['code'] ) { + // TRY with CATCH. + return; + } + + if ( \T_FINALLY === $this->tokens[ $trailingContent ]['code'] && \T_CATCH === $this->tokens[ $stackPtr ]['code'] ) { + // CATCH with FINALLY. + return; + } + + if ( \T_FINALLY === $this->tokens[ $trailingContent ]['code'] && \T_TRY === $this->tokens[ $stackPtr ]['code'] ) { + // TRY with FINALLY. + return; + } + + if ( \T_CLOSE_TAG === $this->tokens[ $trailingContent ]['code'] ) { + // At the end of the script or embedded code. + return; + } + + if ( isset( $this->tokens[ $trailingContent ]['scope_condition'] ) + && \T_CLOSE_CURLY_BRACKET === $this->tokens[ $trailingContent ]['code'] + ) { + // Another control structure's closing brace. + $owner = $this->tokens[ $trailingContent ]['scope_condition']; + if ( isset( Collections::closedScopes()[ $this->tokens[ $owner ]['code'] ] ) === true ) { + // The next content is the closing brace of a function, class, interface or trait + // so normal function/class rules apply and we can ignore it. + return; + } + + if ( ( $this->tokens[ $scopeCloser ]['line'] + 1 ) !== $this->tokens[ $trailingContent ]['line'] ) { + // TODO: Won't cover following case: "} echo 'OK';". + $error = 'Blank line found after control structure'; + $fix = $this->phpcsFile->addFixableError( $error, $scopeCloser, 'BlankLineAfterEnd' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + $i = ( $scopeCloser + 1 ); + while ( $this->tokens[ $i ]['line'] !== $this->tokens[ $trailingContent ]['line'] ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + ++$i; + } + + // TODO: Instead a separate error should be triggered when content comes right after closing brace. + if ( \T_COMMENT !== $this->tokens[ $scopeCloser ]['code'] + && isset( Tokens::$phpcsCommentTokens[ $this->tokens[ $scopeCloser ]['code'] ] ) === false + ) { + $this->phpcsFile->fixer->addNewlineBefore( $trailingContent ); + } + $this->phpcsFile->fixer->endChangeset(); + } + } + } + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php new file mode 100644 index 00000000..b54d3c42 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php @@ -0,0 +1,63 @@ +getTokens(); + $property_adjusted = false; + + // Check for `::class` and don't ignore new lines in that case. + if ( true === $this->ignoreNewlines + && \T_DOUBLE_COLON === $tokens[ $stackPtr ]['code'] + ) { + $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( \T_STRING === $tokens[ $next_non_empty ]['code'] + && 'class' === strtolower( $tokens[ $next_non_empty ]['content'] ) + ) { + $property_adjusted = true; + $this->ignoreNewlines = false; + } + } + + $return = parent::process( $phpcsFile, $stackPtr ); + + if ( true === $property_adjusted ) { + $this->ignoreNewlines = true; + } + + return $return; + } +} diff --git a/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php new file mode 100644 index 00000000..78243795 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -0,0 +1,60 @@ + + + + WordPress Coding Standards + + + + + + diff --git a/vendor/wp-coding-standards/wpcs/composer.json b/vendor/wp-coding-standards/wpcs/composer.json new file mode 100644 index 00000000..4a287cba --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/composer.json @@ -0,0 +1,88 @@ +{ + "name": "wp-coding-standards/wpcs", + "type": "phpcodesniffer-standard", + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "WordPress" + ], + "license": "MIT", + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "require": { + "php": ">=5.4", + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "squizlabs/php_codesniffer": "^3.7.2", + "phpcsstandards/phpcsutils": "^1.0.8", + "phpcsstandards/phpcsextra": "^1.1.0" + }, + "require-dev": { + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "php-parallel-lint/php-console-highlighter": "^1.0.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git" + ], + "check-cs": [ + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" + ], + "fix-cs": [ + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" + ], + "run-tests": [ + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" + ], + "coverage": [ + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" + ], + "check-complete": [ + "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPress" + ], + "check-complete-strict": [ + "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness ./WordPress" + ], + "check-all": [ + "@lint", + "@check-cs", + "@run-tests", + "@check-complete-strict" + ] + }, + "scripts-descriptions": { + "lint": "Lint PHP files against parse errors.", + "check-cs": "Run the PHPCS script against the entire codebase.", + "fix-cs": "Run the PHPCBF script to fix all the autofixable violations on the codebase.", + "run-tests": "Run all the unit tests for the WordPress Coding Standards sniffs without code coverage.", + "coverage": "Run all the unit tests for the WordPress Coding Standards sniffs with code coverage.", + "check-complete": "Check if all the sniffs have tests.", + "check-complete-strict": "Check if all the sniffs have unit tests and XML documentation.", + "check-all": "Run all checks (lint, phpcs, feature completeness) and tests." + }, + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki", + "source": "https://github.com/WordPress/WordPress-Coding-Standards" + } +} diff --git a/vendor/wp-coding-standards/wpcs/phpcs.xml.dist.sample b/vendor/wp-coding-standards/wpcs/phpcs.xml.dist.sample new file mode 100644 index 00000000..b1ddd448 --- /dev/null +++ b/vendor/wp-coding-standards/wpcs/phpcs.xml.dist.sample @@ -0,0 +1,153 @@ + + + + A custom set of rules to check for a WPized WordPress project + + + + . + + + /docroot/wp-admin/* + /docroot/wp-includes/* + /docroot/wp-*.php + /docroot/index.php + /docroot/xmlrpc.php + /docroot/wp-content/plugins/* + + + /vendor/* + + + /node_modules/* + + + *.min.js + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /path/to/Tests/*Test\.php + + + /path/to/Tests/*Test\.php + + +