基础代码

This commit is contained in:
2021-02-26 22:23:13 +08:00
parent 7884df52f0
commit a719feebba
2585 changed files with 328263 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
# Change Log for the WPThemeReview PHP_CodeSniffer standard
All notable changes to this project will be documented in this file.
This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/).
## [Unreleased]
_No documentation available about unreleased changes as of yet._
## [0.2.1] - 2019-11-17
### Changed
- Added new `$in_list` parameter to the `PrefixAllGlobalsSniff::process_variable_assignment()` method, so that the method is compatible with the upstream WPCS method (changed in WPCS 2.2.0).
- Updated the minimum version requirement for the WordPress Coding Standards dependency to version 2.2.0.
- Replaced the deprecated `WordPress.WP.TimezoneChange` with `WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set`
- Enforce the array short syntax when writing sniffs.
- Travis: Minor build script tweaks.
## [0.2.0] - 2019-07-17
### Added
- New `WPThemeReview.Templates.ReservedFileNamePrefix` sniff: checks if the template file is using a prefix which would cause WP to interpret it as a specialized template, meant to apply to only one page on the site.
- New `WPThemeReview.Privacy.ShortenedURLs` sniff: detects the usage of shortened URLs. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#privacy).
- New `WPThemeReview.CoreFunctionality.PostsPerPage` sniff: adds a warning when a high pagination limit is detected, or if `-1` is used in `posts_per_page` setting while querying posts, due to detrimental effects it has on the query speed.
- New `WPThemeReview.CoreFunctionality.PrefixAllGlobals` sniff, which extends the `WordPress.NamingConventions.PrefixAllGlobals`. The new sniff overloads the prefix check for variables only and will bow out if the file being scanned has a typical theme template file name. For all other files, it will fall through to the WPCS native sniff.
Notes:
* The new sniff adds a public `$allowed_folders` property to whitelist files in specific folders of a theme as template files.
The `ruleset.xml` file sets this property to a limited set of folders whitelisted by default.
* Similar to the WPCS `FileNameSniff`, this sniff does not currently allow for mimetype sublevel only theme template file names, such as `plain.php`.
- Added two new groups to the restricted functions group: `editor-blocks` and `cron-functionality` in the `WPThemeReview.PluginTerritory.ForbiddenFunctions` sniff, which will check against core editor blocks being registered in the themes, and against the usage of cron functions in the theme respectively.
- The `WordPress.PHP.IniSet` rule was added to the ruleset to check against themes setting ini configuration during runtime.
- The `WordPress.WP.DeprecatedParameterValues` rule was added to the ruleset about usage of deprecated parameter values in WP functions. The sniff will suggest an alternative based on the parameter passed.
### Changed
- Added XSD schema tags and validated the ruleset against schema (PHPCS 3.2+/3.3.2+).
- Updated the minimum version requirement for the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md) dependency to version 2.1.0.
- Updated the suggested dealerdirect/phpcodesniffer-composer-installer version.
- Updated the `WPThemeReview` codebase, where relevant, for compatibility with WPCS 2.0+.
- Ruleset tweaks: limit PHPCompatibility to PHP files
- Composer tweaks: improve readability of script section
- Travis: Run the code style related and ruleset checks in separate [stages](https://docs.travis-ci.com/user/build-stages/).
- Travis: Unit tests are now also run against PHP 7.4 (dev)
- Travis: Test against high/low WPCS versions due to sniffs that are extending the WPCS native sniffs.
- Travis: Minor tweaks to the Travis script.
### Removed
- Remove `encoding` from the ruleset. The default `encoding` as of PHPCS 3.0.0 is `utf-8`, so we don't actually need to set this.
### Fixed
- Removal of HTML from error message about adding menu pages in `WPThemeReview.PluginTerritory.NoAddAdminPages` sniff.
- Minor grammar changes in the ruleset.
## [0.1.0] - 2018-10-31
### Added
#### Native sniffs
- `WPThemeReview.CoreFunctionality.FileInclude`: checks if a theme uses `include(_once)` or `require(_once)` when `get_template_part()` should be used. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features).
- `WPThemeReview.CoreFunctionality.NoDeregisterCoreScript`: checks if a theme deregisters core scripts (javascript). [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#stylesheets-and-scripts).
- `WPThemeReview.CoreFunctionality.NoFavicon`: checks if a theme is hard coding favicons instead of using core implementation. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features).
- `WPThemeReview.CoreFunctionality.NoTitleTag`: checks if a theme is using a `<title>` tag instead of `add_theme_support( 'title-tag' )`. `<svg>` tag can use a `<title>` tag. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features).
- `WPThemeReview.PluginTerritory.AdminBarRemoval`: checks if a theme is removing the WP admin bar. This sniff was originally part of `WordPressCS`. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features).
- `WPThemeReview.PluginTerritory.ForbiddenFunctions`: checks if a theme is using functions that fall under plugin territory. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#presentation-vs-functionality).
- `WPThemeReview.PluginTerritory.NoAddAdminPages`: checks if a theme is using `add_..._page()` functions, with the exception of `add_theme_page()`. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#admin-menu).
- `WPThemeReview.PluginTerritory.SessionFunctionsUsage`: Prevents the usage of the session functions in themes. This sniff was originally part of `WordPressCS`.
- `WPThemeReview.PluginTerritory.SessionVariableUsage`: Prevents the usage of the session variables in themes. This sniff was originally part of `WordPressCS`.
- `WPThemeReview.Plugins.CorrectTGMPAVersion`: verifies that if the [TGM Plugin Activation](http://tgmpluginactivation.com/) library is included, the correct version is used. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#admin-menu).
- `WPThemeReview.ThouShallNotUse.ForbiddenIframe`: checks if a theme is using `<iframe>`. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#info).
- `WPThemeReview.ThouShallNotUse.NoAutoGenerate`: checks if a theme has been generated using theme generators.
#### Other sniffs in the ruleset
- `PHPCompatibilityWP`: added as an entire ruleset, checking that the theme is compatible with PHP 5.2 and above.
- `Generic.PHP.DisallowShortOpenTag`: prohibits the usage of the PHP short open tags. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#additional-checks).
- `Generic.PHP.DisallowAlternativePHPTags`: disallows the usage of alternative PHP open tags (`<%` and similar).
- `Squiz.WhiteSpace.SuperfluousWhitespace.StartFile`: files starting with a PHP open tag shouldn't have a whitespace preceding it, to prevent possible `headers already sent` errors.
- `PSR2.Files.ClosingTag.NotAllowed`: files should omit the closing PHP tag at the end of a file, to prevent possible `headers already sent` errors.
- `Internal.LineEndings.Mixed`: mixed line endings are not allowed. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#line-endings).
- `Internal.Tokenizer.Exception`: minified scripts or files should have original files included. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#stylesheets-and-scripts).
- `Generic.Files.ByteOrderMark`: no ByteOrderMark allowed - important to prevent issues with content being sent before headers.
- `Generic.CodeAnalysis.EmptyStatement`: prohibits empty statements in the code (empty conditionals for instance).
- `WordPress.CodeAnalysis.EmptyStatement`: prohibits empty PHP statements (empty PHP tags with no content or double semi-colons).
- `WordPress.WP.I18n`: check that the I18N functions are used correctly. This sniff can also check the text domain, provided it's passed to `PHPCS`. See the [documentation](https://github.com/WordPress/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#internationalization-setting-your-text-domain) for more details.
- `WordPress.WP.EnqueuedResources`: hard coding of scripts and styles is prohibited. They should be enqueued.
- `WordPress.Security.PluginMenuSlug`: prevent path disclosure when using add_theme_page().
- `Generic.PHP.NoSilencedErrors`: usage of Error Control Operator `@` is forbidden in a theme.
- `WordPress.DB.RestrictedClasses`: the WP abstraction layer should be used to query database if needed.
- `WordPress.DB.RestrictedFunctions`: the WP abstraction layer should be used to query database if needed.
- `WordPress.DB.PreparedSQL`: all SQL queries should be prepared as close to the time of querying the database as possible.
- `WordPress.DB.PreparedSQLPlaceholders`: verify that placeholders in prepared queries are used correctly.
- `WordPress.Security.ValidatedSanitizedInput`: validate and/or sanitize untrusted data before using it.
- `WordPress.Security.EscapeOutput`: all untrusted data should be escaped before output - warning, since translations don't have to be escaped.
- `Generic.PHP.BacktickOperator`: prohibit the use of the backtick operator.
- `WordPress.WP.GlobalVariablesOverride`: prohibit overwriting of WordPress global variables.
- `Squiz.PHP.Eval.Discouraged`: prohibit the use of the eval() PHP language construct.
- `Generic.PHP.DiscourageGoto.Found`: prohibit the use of the `goto` PHP language construct.
- `WordPress.WP.DeprecatedClasses`: check for use of deprecated WordPress classes.
- `WordPress.WP.DeprecatedFunctions`: check for use of deprecated WordPress functions.
- `WordPress.WP.DeprecatedParameters`: check for use of deprecated WordPress function parameters.
- `WordPress.WP.DiscouragedConstants`: check for deprecated WordPress constants.
- `WordPress.NamingConventions.PrefixAllGlobals`: verify that everything in the global namespace is prefixed. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#code). This rule will only work if a prefix is passed. See the [documentation](https://github.com/WordPress/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace) for more details.
- `WordPress.WP.CapitalPDangit`: check for correct spelling of WordPress. [Handbook rule](https://make.wordpress.org/themes/handbook/review/required/#naming)
- `WordPress.WP.TimezoneChange`: themes should never touch the timezone.
[Unreleased]: https://github.com/WPTRT/WPThemeReview/compare/master...HEAD
[0.2.1]: https://github.com/WPTRT/WPThemeReview/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/WPTRT/WPThemeReview/compare/0.1.0...0.2.0
[0.1.0]: https://github.com/WPTRT/WPThemeReview/compare/1dabb9876caf78209849a01381c0b863ce583d07...0.1.0
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 WordPress Theme Review Team and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+118
View File
@@ -0,0 +1,118 @@
<div aria-hidden="true">
[![Latest Version](https://poser.pugx.org/wptrt/wpthemereview/v/stable)](https://packagist.org/packages/wptrt/wpthemereview)
[![Travis Build Status](https://travis-ci.org/WPTRT/WPThemeReview.svg?branch=master)](https://travis-ci.org/WPTRT/WPThemeReview)
[![Last Commit to Unstable](https://img.shields.io/github/last-commit/WPTRT/WPThemeReview/develop.svg)](https://github.com/WPTRT/WPThemeReview/commits/develop)
[![Minimum PHP Version](https://img.shields.io/packagist/php-v/wptrt/wpthemereview.svg?maxAge=3600)](https://packagist.org/packages/wptrt/wpthemereview)
[![Tested on PHP 5.3 to nightly](https://img.shields.io/badge/tested%20on-PHP%205.3%20|%205.4%20|%205.5%20|%205.6%20|%207.0%20|%207.1%20|%207.2%20|%20nightly-green.svg?maxAge=2419200)](https://travis-ci.org/WPTRT/WPThemeReview)
[![License: MIT](https://poser.pugx.org/wptrt/wpthemereview/license)](https://github.com/WPTRT/WPThemeReview/blob/develop/LICENSE)
[![Number of Contributors](https://img.shields.io/github/contributors/WPTRT/WPThemeReview.svg?maxAge=3600)](https://github.com/WPTRT/WPThemeReview/graphs/contributors)
</div>
# WPThemeReview Standard for PHP_CodeSniffer
* [Introduction](#introduction)
* [Requirements](#requirements)
* [Installation](#installation)
+ [Installing WPThemeReview globally](#installing-wpthemereview-globally)
+ [Installing WPThemeReview as a project dependency](#installing-wpthemereview-as-a-project-dependency)
+ [Checking your installation was successful](#checking-your-installation-was-successful)
* [Using the WPThemeReview standard](#using-the-wpthemereview-standard)
* [Contributing](#contributing)
* [License](#license)
## Introduction
WordPress Themes for which a hosting application has been made for the theme to be hosted in the theme repository on [wordpress.org](https://wordpress.org/themes/) have to comply with [a set of requirements](https://make.wordpress.org/themes/handbook/review/required/) before such an application can be approved.
Additionally, there are also [recommendations](https://make.wordpress.org/themes/handbook/review/recommended/) for best practices for themes.
This project attempts to automate the code analysis part of the [Theme Review Process](https://make.wordpress.org/themes/handbook/review/) as much as possible using static code analysis.
[PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) is the base tool upon which this project is build and is a PHP command-line tool.
**_This project is a work in progress and passing the checks is no guarantee that your theme will be approved._**
## Requirements
The WPThemeReview Standard requires:
* PHP 5.4 or higher.
* [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) version **3.3.1** or higher.
* [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) version **2.1.0** or higher.
* [PHPCompatibilityWP](https://github.com/PHPCompatibility/PHPCompatibilityWP) version **2.0.0** or higher.
## Installation
The only supported installation method is via [Composer](https://getcomposer.org/).
1. Make sure you have PHP installed on your system.
2. If not installed yet, install [Composer](https://getcomposer.org/download/).
### Installing WPThemeReview globally
If you would like to have the WPThemeReview standard available to all projects on your system, you can install it in a central location.
From the command-line, run the following command:
```bash
$ php composer.phar global require wptrt/wpthemereview dealerdirect/phpcodesniffer-composer-installer
```
### Installing WPThemeReview as a project dependency
If you use Composer to manage dependencies from your project anyway or are considering using it, you can also choose to install the WPThemeReview standard for an individual project.
From the command-line, run the following command from the root directory of your project:
```bash
$ php composer.phar require --dev wptrt/wpthemereview:* dealerdirect/phpcodesniffer-composer-installer:^0.5.0
```
> Note:
> * The `--dev` means that WPThemeReview will be installed as a development requirement, not as a requirement for using the Theme.
> * The second package - `dealerdirect/phpcodesniffer-composer-installer` -, is a Composer plugin which will automatically sort out that PHP_CodeSniffer recognizes the WPThemeReview standard and the various WordPress standards.
### Checking your installation was successful
```bash
# For a global install:
$ phpcs -i
# For a project install:
$ vendor/bin/phpcs -i
```
If everything went well, the output should look something like this:
```
The installed coding standards are MySource, PEAR, PSR1, PSR12, PSR2, Squiz, Zend, PHPCompatibility,
PHPCompatibilityParagonieRandomCompat, PHPCompatibilityParagonieSodiumCompat, PHPCompatibilityWP,
WordPress, WordPress-Core, WordPress-Docs, WordPress-Extra and WPThemeReview
```
## Using the WPThemeReview standard
You can now test your theme code against the WPThemeReview standard by running the following command from the root directory of your theme:
```bash
# For a global install:
$ phpcs -p . --standard=WPThemeReview
# For a project install:
$ vendor/bin/phpcs -p . --standard=WPThemeReview
```
If any issues are found, PHP_CodeSniffer will display a report with all the errors (must fix) and warnings (recommended to fix) for each file.
More information about running PHP_CodeSniffer can be found in the [PHP_CodeSniffer Wiki](https://github.com/squizlabs/PHP_CodeSniffer/wiki).
## Contributing
See [CONTRIBUTING](.github/CONTRIBUTING.md), including information about [unit testing](.github/CONTRIBUTING.md#unit-testing) the standard.
## License
See [LICENSE](LICENSE) (MIT).
@@ -0,0 +1,69 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check if a theme uses include(_once) or require(_once) when get_template_part() should be used.
*
* @link https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features
*
* @since 0.1.0
*/
class FileIncludeSniff implements Sniff {
/**
* A list of files to skip.
*
* @var array
*/
protected $file_whitelist = [
'functions.php' => true,
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return Tokens::$includeTokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$token = $tokens[ $stackPtr ];
$file_name = basename( $phpcsFile->getFileName() );
if ( ! isset( $this->file_whitelist[ $file_name ] ) ) {
$phpcsFile->addWarning(
'Check that %s is not being used to load template files. "get_template_part()" should be used to load template files.',
$stackPtr,
'FileIncludeFound',
[ $token['content'] ]
);
}
}
}
@@ -0,0 +1,234 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use WordPressCS\WordPress\AbstractFunctionParameterSniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Forbids deregistering of core scripts (javascript).
*
* @link https://make.wordpress.org/themes/handbook/review/required/#stylesheets-and-scripts
*
* @since 0.1.0
*/
class NoDeregisterCoreScriptSniff extends AbstractFunctionParameterSniff {
/**
* The group name for this group of functions.
*
* @since 0.1.0
*
* @var string
*/
protected $group_name = 'wp_deregister_script';
/**
* Array of function and script handles used in core.
*
* {@internal List was probably based on the documentation of the wp_enqueue_script function.
* List needs further verification and update.
*
* @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/}}
*
* @since 0.1.0
*
* @var array
*/
protected $target_functions = [
'wp_deregister_script' => [
'jcrop' => true,
'swfobject' => true,
'swfupload' => true,
'swfupload-degrade' => true,
'swfupload-queue' => true,
'swfupload-handlers' => true,
'jquery' => true,
'jquery-form' => true,
'jquery-color' => true,
'jquery-masonry' => true,
'masonry' => true,
'jquery-ui-core' => true,
'jquery-ui-widget' => true,
'jquery-ui-accordion' => true,
'jquery-ui-autocomplete' => true,
'jquery-ui-button' => true,
'jquery-ui-datepicker' => true,
'jquery-ui-dialog' => true,
'jquery-ui-draggable' => true,
'jquery-ui-droppable' => true,
'jquery-ui-menu' => true,
'jquery-ui-mouse' => true,
'jquery-ui-position' => true,
'jquery-ui-progressbar' => true,
'jquery-ui-selectable' => true,
'jquery-ui-resizable' => true,
'jquery-ui-selectmenu' => true,
'jquery-ui-sortable' => true,
'jquery-ui-slider' => true,
'jquery-ui-spinner' => true,
'jquery-ui-tooltip' => true,
'jquery-ui-tabs' => true,
'jquery-effects-core' => true,
'jquery-effects-blind' => true,
'jquery-effects-bounce' => true,
'jquery-effects-clip' => true,
'jquery-effects-drop' => true,
'jquery-effects-explode' => true,
'jquery-effects-fade' => true,
'jquery-effects-fold' => true,
'jquery-effects-highlight' => true,
'jquery-effects-pulsate' => true,
'jquery-effects-scale' => true,
'jquery-effects-shake' => true,
'jquery-effects-slide' => true,
'jquery-effects-transfer' => true,
'wp-mediaelement' => true,
'schedule' => true,
'suggest' => true,
'thickbox' => true,
'hoverIntent' => true,
'jquery-hotkeys' => true,
'sack' => true,
'quicktags' => true,
'iris' => true,
'farbtastic' => true,
'colorpicker' => true,
'tiny_mce' => true,
'autosave' => true,
'wp-ajax-response' => true,
'wp-lists' => true,
'common' => true,
'editorremov' => true,
'editor-functions' => true,
'ajaxcat' => true,
'admin-categories' => true,
'admin-tags' => true,
'admin-custom-fields' => true,
'password-strength-meter' => true,
'admin-comments' => true,
'admin-users' => true,
'admin-forms' => true,
'xfn' => true,
'upload' => true,
'postbox' => true,
'slug' => true,
'post' => true,
'page' => true,
'link' => true,
'comment' => true,
'comment-reply' => true,
'admin-gallery' => true,
'media-upload' => true,
'admin-widgets' => true,
'word-count' => true,
'theme-preview' => true,
'json2' => true,
'plupload' => true,
'plupload-all' => true,
'plupload-html4' => true,
'plupload-html5' => true,
'plupload-flash' => true,
'plupload-silverlight' => true,
'underscore' => true,
'backbone' => true,
'imagesloaded' => true,
],
];
/**
* Process the parameters of a matched function.
*
* @since 0.1.0
*
* @param int $stackPtr The position of the current token in the stack.
* @param array $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return void
*/
public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
if ( ! isset( $parameters[1] ) ) {
return;
}
$matched_parameter = $this->strip_quotes( $parameters[1]['raw'] );
$first_param_token = $this->phpcsFile->findNext(
Tokens::$emptyTokens,
$parameters[1]['start'],
( $parameters[1]['end'] + 1 ),
true
);
if ( isset( $this->target_functions[ $matched_content ][ $matched_parameter ] ) ) {
$this->throw_prohibited_error( $first_param_token, [ $matched_parameter ] );
return;
}
// More extensive check to prevent people circumventing the sniff.
$text = '';
$found_variable_token = false;
for ( $i = $parameters[1]['start']; $i <= $parameters[1]['end']; $i++ ) {
if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) === false ) {
if ( T_VARIABLE === $this->tokens[ $i ]['code']
|| T_STRING === $this->tokens[ $i ]['code']
) {
$found_variable_token = true;
}
continue;
}
$text .= $this->strip_quotes( $this->tokens[ $i ]['content'] );
}
if ( isset( $this->target_functions[ $matched_content ][ $text ] ) ) {
$this->throw_prohibited_error( $first_param_token, [ $text ] );
return;
}
if ( true === $found_variable_token ) {
$this->throw_variable_handle_warning( $first_param_token, [ $matched_content, $matched_parameter ] );
}
}
/**
* Throw the error for deregistering a core script.
*
* @param int $stackPtr The position of the first non-empty parameter token in the stack.
* @param array $data Optional input for the data replacements.
*/
public function throw_prohibited_error( $stackPtr, $data = [] ) {
$this->phpcsFile->addError(
'Deregistering core script "%s" is prohibited.',
$stackPtr,
'Found',
$data
);
}
/**
* Throw a warning when a variable handle for deregistering a script is detected.
*
* @param int $stackPtr The position of the first non-empty parameter token in the stack.
* @param array $data Optional input for the data replacements.
*/
public function throw_variable_handle_warning( $stackPtr, $data = [] ) {
$this->phpcsFile->addWarning(
'Deregistering core scripts is prohibited. A variable script handle was found. Inspection of the %s() call needed. Found: %s',
$stackPtr,
'VariableHandleFound',
$data
);
}
}
@@ -0,0 +1,117 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check for hardcoded favicons instead of using core implementation.
*
* @link https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features
*
* @since 0.1.0
*/
class NoFaviconSniff implements Sniff {
/**
* Regex template.
*
* Will be parsed together with the attribute blacklist in the register() method.
*
* @var string
*/
const REGEX_TEMPLATE = '` (?:%s)`i';
/**
* (Partial) Regex template for recognizing a HTML attribute.
*
* Will be parsed together with the attribute blacklist in the register() method.
*
* @var string
*/
const REGEX_ATTR_TEMPLATE = '%1$s=[\'"](?:%2$s)[\'"]';
/**
* List of link and meta attributes that are blacklisted.
*
* @var array
*/
protected $attribute_blacklist = [
'rel' => [
'icon',
'shortcut icon',
'bookmark icon',
'apple-touch-icon',
'apple-touch-icon-precomposed',
],
'name' => [
'msapplication-config',
'msapplication-TileImage',
'msapplication-square70x70logo',
'msapplication-square150x150logo',
'msapplication-wide310x150logo',
'msapplication-square310x310logo',
],
];
/**
* The regex to catch the blacklisted attributes.
*
* @var string
*/
protected $favicon_regex;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
// Build the regex to be used only once.
$regex_parts = [];
foreach ( $this->attribute_blacklist as $key => $values ) {
$values = array_map( 'preg_quote', $values, array_fill( 0, count( $values ), '`' ) );
$values = implode( '|', $values );
$regex_parts[] = sprintf( self::REGEX_ATTR_TEMPLATE, preg_quote( $key, '`' ), $values );
}
$this->favicon_regex = sprintf( self::REGEX_TEMPLATE, implode( '|', $regex_parts ) );
return Tokens::$textStringTokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$token = $tokens[ $stackPtr ];
if ( preg_match( $this->favicon_regex, $token['content'] ) > 0 ) {
$phpcsFile->addError(
'Code for favicon found. Favicons are handled by the "Site Icon" setting in the customizer since WP version 4.3.',
$stackPtr,
'NoFavicon'
);
}
}
}
@@ -0,0 +1,103 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Restricts the use of the <title> tag, unless it is within a <svg> tag.
*
* @link https://make.wordpress.org/themes/handbook/review/required/
*
* @since 0.1.0
*/
class NoTitleTagSniff implements Sniff {
/**
* Property to keep track of whether a <svg> open tag has been encountered.
*
* @var array
*/
private $in_svg;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return Tokens::$textStringTokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$content = $tokens[ $stackPtr ]['content'];
$filename = $phpcsFile->getFileName();
// Set to false if it is the first time this sniff is run on a file.
if ( ! isset( $this->in_svg[ $filename ] ) ) {
$this->in_svg[ $filename ] = false;
}
// No need to check an empty string.
if ( '' === trim( $content ) ) {
return;
}
// Are we in a <svg> tag ?
if ( true === $this->in_svg[ $filename ] ) {
if ( false === strpos( $content, '</svg>' ) ) {
return;
} else {
// Make sure we check any content on this line after the closing svg tag.
$this->in_svg[ $filename ] = false;
$content = trim( substr( $content, ( strpos( $content, '</svg>' ) + 6 ) ) );
}
}
// We're not in svg, so check if it there's a <svg> open tag on this line.
if ( false !== strpos( $content, '<svg' ) ) {
if ( false === strpos( $content, '</svg>' ) ) {
// Skip the next lines until the closing svg tag, but do check any content
// on this line before the svg tag.
$this->in_svg[ $filename ] = true;
$content = trim( substr( $content, 0, ( strpos( $content, '<svg' ) ) ) );
} else {
// Ok, we have open and close svg tag on the same line with possibly content before and/or after.
$before = trim( substr( $content, 0, ( strpos( $content, '<svg' ) ) ) );
$after = trim( substr( $content, ( strpos( $content, '</svg>' ) + 6 ) ) );
$content = $before . $after;
}
}
// Now let's do the check for the <title> tag.
if ( false !== strpos( $content, '<title' ) ) {
$phpcsFile->addError(
"The title tag must not be used. Use add_theme_support( 'title-tag' ) instead.",
$stackPtr,
'TagFound'
);
}
}
}
@@ -0,0 +1,51 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use WordPressCS\WordPress\Sniffs\WP\PostsPerPageSniff as WPCSPostsPerPageSniff;
/**
* Flag returning high or infinite posts_per_page.
*
* This sniff extends the upstream WPCS PostsPerPageSniff. The difference is that this
* sniff will not only warn against a high pagination limit, but will also warn against
* using -1 as posts_per_page setting while querying posts, due to detrimental effects
* it has on query speed.
*
* @link https://github.com/WPTRT/WPThemeReview/issues/147
*
* @since 0.2.0
*/
class PostsPerPageSniff extends WPCSPostsPerPageSniff {
/**
* 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 mixed FALSE if no match, TRUE if matches, STRING if matches
* with custom error message passed to ->process().
*/
public function callback( $key, $val, $line, $group ) {
$this->posts_per_page = (int) $this->posts_per_page;
if ( '-1' === $val ) {
return 'Disabling pagination is not advised, avoid setting `%s` to `%s`';
}
if ( $val > $this->posts_per_page ) {
return 'Detected high pagination limit, `%s` is set to `%s`';
}
return false;
}
}
@@ -0,0 +1,191 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\CoreFunctionality;
use WordPressCS\WordPress\Sniffs\NamingConventions\PrefixAllGlobalsSniff as WPCSPrefixAllGlobalsSniff;
/**
* Verify that everything defined in the global namespace is prefixed with a theme specific prefix.
*
* This sniff extends the upstream WPCS PrefixAllGlobalsSniff. The differences are:
* - For non-prefixed global variables, an error will only be thrown when the variable
* is created outside of a theme template file.
*
* @link https://github.com/WPTRT/WPThemeReview/issues/205
* @link https://github.com/WPTRT/WPThemeReview/issues/201
* @link https://github.com/WPTRT/WPThemeReview/issues/200
*
* The sniff does not currently allow for mimetype sublevel only file names,
* such as `plain.php`.
*
* @since 0.2.0
*/
class PrefixAllGlobalsSniff extends WPCSPrefixAllGlobalsSniff {
/**
* Regex to recognize more complex typical theme template file names.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#custom-taxonomies
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#custom-post-types
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#embeds
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#attachment
* @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
* @link https://wphierarchy.com/
* @link https://en.wikipedia.org/wiki/Media_type#Naming
*
* @since 0.2.0
*
* @var string
*/
const COMPLEX_THEME_TEMPLATE_NAME_REGEX = '`
^ # Anchor to the beginning of the string.
(?:
# Template file prefixes with subtype.
(?:archive|author|category|content|embed|page|single|tag|taxonomy)
-[^\.]+ # These need to be followed by a dash and some chars.
|
(?:application|audio|example|font|image|message|model|multipart|text|video) #Top-level mime-types
(?:_[^\.]+)? # Optionally followed by an underscore and a sub-type.
)\.php$ # End in .php and anchor to the end of the string.
`Dx';
/**
* The list of allowed folders to check the file path against.
*
* The WPThemereview standards contains a base set for this property in the ruleset.xml.
* This array can be extended in a custom ruleset.
*
* @since 0.2.0
*
* @var array
*/
public $allowed_folders = [];
/**
* List of plain template file names.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
* @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
* @link https://wphierarchy.com/
* @link https://en.wikipedia.org/wiki/Media_type#Naming
*
* @since 0.2.0
*
* @var array
*/
protected $simple_theme_template_file_names = [
// Plain primary template file names.
'404.php' => true,
'archive.php' => true,
'home.php' => true,
'index.php' => true,
'page.php' => true,
'search.php' => true,
'single.php' => true,
'singular.php' => true,
// Plain secondary template file names.
'attachment.php' => true,
'author.php' => true,
'category.php' => true,
'date.php' => true,
'embed.php' => true,
'front-page.php' => true,
'single-post.php' => true,
'tag.php' => true,
'taxonomy.php' => true,
// Plain partial and miscellaneous template file names.
'comments.php' => true,
'footer.php' => true,
'header.php' => true,
'sidebar.php' => true,
// Top-leve mime types.
'application.php' => true,
'audio.php' => true,
'example.php' => true,
'font.php' => true,
'image.php' => true,
'message.php' => true,
'model.php' => true,
'multipart.php' => true,
'text.php' => true,
'video.php' => true,
];
/**
* Check that defined global variables are prefixed.
*
* This overloads the parent method to allow for non-prefixed variables to be declared
* in template files as those are included from within a function and therefore would be
* local to that function.
*
* @since 0.2.0
* @since 0.2.1 Added $in_list parameter as introduced in WPCS 2.2.0.
*
* @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 ) {
// Usage of `strip_quotes` is to ensure `stdin_path` passed by IDEs does not include quotes.
$file = $this->strip_quotes( $this->phpcsFile->getFileName() );
$fileName = basename( $file );
if ( \defined( '\PHP_CODESNIFFER_IN_TESTS' ) ) {
$fileName = str_replace( '.inc', '.php', $fileName );
}
// Don't process in case of a template file or folder.
if ( isset( $this->simple_theme_template_file_names[ $fileName ] ) === true ) {
return;
}
if ( preg_match( self::COMPLEX_THEME_TEMPLATE_NAME_REGEX, $fileName ) === 1 ) {
return;
}
if ( $this->is_from_allowed_folder( $file ) ) {
return;
}
// Not a typical template file name, defer to the prefix checking in the parent sniff.
return parent::process_variable_assignment( $stackPtr, $in_list );
}
/**
* Checks if the given file path is located in the $allowed_folders array.
*
* @since 0.2.0
*
* @param string $path Full path of the sniffed file.
* @return boolean
*/
private function is_from_allowed_folder( $path ) {
if ( empty( $this->allowed_folders ) || ! is_array( $this->allowed_folders ) ) {
return false;
}
foreach ( $this->allowed_folders as $folder ) {
if ( strrpos( $path, DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR ) !== false ) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,417 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\PluginTerritory;
use WordPressCS\WordPress\AbstractFunctionParameterSniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Discourages removal of the admin bar.
*
* @link https://make.wordpress.org/themes/handbook/review/required/#core-functionality-and-features
*
* @since WPCS 0.3.0
* @since WPCS 0.11.0 - Extends the WPCS native `AbstractFunctionParameterSniff` class.
* - Added the $remove_only property.
* - Now also sniffs for manipulation of the admin bar visibility through CSS.
* @since WPCS 0.13.0 Class name changed: this class is now namespaced.
*
* @since TRTCS 0.1.0 As this sniff will be removed from WPCS in version 2.0, the
* sniff has been cherry-picked into the WPThemeReview standard.
*/
class AdminBarRemovalSniff extends AbstractFunctionParameterSniff {
/**
* A list of tokenizers this sniff supports.
*
* @since WPCS 0.11.0
*
* @var array
*/
public $supportedTokenizers = [ 'PHP', 'CSS' ];
/**
* Whether or not the sniff only checks for removal of the admin bar
* or any manipulation to the visibility of the admin bar.
*
* Defaults to true: only check for removal of the admin bar.
* Set to false to check for any form of manipulation of the visibility
* of the admin bar.
*
* @since WPCS 0.11.0
*
* @var bool
*/
public $remove_only = true;
/**
* Functions this sniff is looking for.
*
* @since WPCS 0.11.0
*
* @var array
*/
protected $target_functions = [
'show_admin_bar' => true,
'add_filter' => true,
];
/**
* CSS properties this sniff is looking for.
*
* @since WPCS 0.11.0
*
* @var array
*/
protected $target_css_properties = [
'visibility' => [
'type' => '!=',
'value' => 'hidden',
],
'display' => [
'type' => '!=',
'value' => 'none',
],
'opacity' => [
'type' => '>',
'value' => 0.3,
],
];
/**
* CSS selectors this sniff is looking for.
*
* @since WPCS 0.11.0
*
* @var array
*/
protected $target_css_selectors = [
'.show-admin-bar',
'#wpadminbar',
];
/**
* Regex template for use with the CSS selectors in combination with PHP text strings.
*
* @since WPCS 0.11.0
*
* @var string
*/
private $target_css_selectors_regex = '`(?:%s).*?\{(.*)$`';
/**
* Property to keep track of whether a <style> open tag has been encountered.
*
* @since WPCS 0.11.0
*
* @var array
*/
private $in_style;
/**
* Property to keep track of whether a one of the target selectors has been encountered.
*
* @since WPCS 0.11.0
*
* @var array
*/
private $in_target_selector;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
$targets = Tokens::$textStringTokens;
// Add CSS style target.
$targets[] = \T_STYLE;
// Set the target selectors regex only once.
$selectors = array_map(
'preg_quote',
$this->target_css_selectors,
array_fill( 0, \count( $this->target_css_selectors ), '`' )
);
// Parse the selectors array into the regex string.
$this->target_css_selectors_regex = sprintf( $this->target_css_selectors_regex, implode( '|', $selectors ) );
// Add function call targets.
$parent = parent::register();
if ( ! empty( $parent ) ) {
$targets[] = \T_STRING;
}
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 ) {
$file_name = $this->phpcsFile->getFileName();
$file_extension = substr( strrchr( $file_name, '.' ), 1 );
if ( 'css' === $file_extension ) {
if ( \T_STYLE === $this->tokens[ $stackPtr ]['code'] ) {
return $this->process_css_style( $stackPtr );
}
} elseif ( isset( Tokens::$textStringTokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) {
/*
* Set $in_style && $in_target_selector to false if it is the first time
* this sniff is run on a file.
*/
if ( ! isset( $this->in_style[ $file_name ] ) ) {
$this->in_style[ $file_name ] = false;
}
if ( ! isset( $this->in_target_selector[ $file_name ] ) ) {
$this->in_target_selector[ $file_name ] = false;
}
return $this->process_text_for_style( $stackPtr, $file_name );
} else {
return parent::process_token( $stackPtr );
}
}
/**
* Process the parameters of a matched function.
*
* @since WPCS 0.11.0
*
* @param int $stackPtr The position of the current token in the stack.
* @param array $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return void
*/
public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
$error = false;
switch ( $matched_content ) {
case 'show_admin_bar':
$error = true;
if ( true === $this->remove_only ) {
if ( 'true' === $parameters[1]['raw'] ) {
$error = false;
}
}
break;
case 'add_filter':
$filter_name = $this->strip_quotes( $parameters[1]['raw'] );
if ( 'show_admin_bar' !== $filter_name ) {
break;
}
$error = true;
if ( true === $this->remove_only && isset( $parameters[2]['raw'] ) ) {
if ( '__return_true' === $this->strip_quotes( $parameters[2]['raw'] ) ) {
$error = false;
}
}
break;
default:
// Left empty on purpose.
break;
}
if ( true === $error ) {
$this->phpcsFile->addError( 'Removal of admin bar is prohibited.', $stackPtr, 'RemovalDetected' );
}
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since WPCS 0.11.0
*
* @param int $stackPtr The position of the current token in the stack.
* @param string $file_name The file name of the current file being processed.
*
* @return void
*/
public function process_text_for_style( $stackPtr, $file_name ) {
$content = trim( $this->tokens[ $stackPtr ]['content'] );
// No need to check an empty string.
if ( '' === $content ) {
return;
}
// Are we in a <style> tag ?
if ( true === $this->in_style[ $file_name ] ) {
if ( false !== strpos( $content, '</style>' ) ) {
// Make sure we check any content on this line before the closing style tag.
$this->in_style[ $file_name ] = false;
$content = trim( substr( $content, 0, strpos( $content, '</style>' ) ) );
}
} elseif ( false !== strpos( $content, '<style' ) ) {
// Ok, found a <style> open tag.
if ( false === strpos( $content, '</style>' ) ) {
// Make sure we check any content on this line after the opening style tag.
$this->in_style[ $file_name ] = true;
$content = trim( substr( $content, ( strpos( $content, '<style' ) + 6 ) ) );
} else {
// Ok, we have open and close style tag on the same line with possibly content within.
$start = ( strpos( $content, '<style' ) + 6 );
$end = strpos( $content, '</style>' );
$content = trim( substr( $content, $start, ( $end - $start ) ) );
unset( $start, $end );
}
} else {
return;
}
// Are we in one of the target selectors ?
if ( true === $this->in_target_selector[ $file_name ] ) {
if ( false !== strpos( $content, '}' ) ) {
// Make sure we check any content on this line before the selector closing brace.
$this->in_target_selector[ $file_name ] = false;
$content = trim( substr( $content, 0, strpos( $content, '}' ) ) );
}
} elseif ( preg_match( $this->target_css_selectors_regex, $content, $matches ) > 0 ) {
// Ok, found a new target selector.
$content = '';
if ( isset( $matches[1] ) && '' !== $matches[1] ) {
if ( false === strpos( $matches[1], '}' ) ) {
// Make sure we check any content on this line before the closing brace.
$this->in_target_selector[ $file_name ] = true;
$content = trim( $matches[1] );
} else {
// Ok, we have the selector open and close brace on the same line.
$content = trim( substr( $matches[1], 0, strpos( $matches[1], '}' ) ) );
}
} else {
$this->in_target_selector[ $file_name ] = true;
}
} else {
return;
}
unset( $matches );
// Now let's do the check for the CSS properties.
if ( ! empty( $content ) ) {
foreach ( $this->target_css_properties as $property => $requirements ) {
if ( false !== strpos( $content, $property ) ) {
$error = true;
if ( true === $this->remove_only ) {
// Check the value of the CSS property.
if ( preg_match( '`' . preg_quote( $property, '`' ) . '\s*:\s*(.+?)\s*(?:!important)?;`', $content, $matches ) > 0 ) {
$value = trim( $matches[1] );
$valid = $this->validate_css_property_value( $value, $requirements['type'], $requirements['value'] );
if ( true === $valid ) {
$error = false;
}
}
}
if ( true === $error ) {
$this->phpcsFile->addError( 'Hiding of the admin bar is not allowed.', $stackPtr, 'HidingDetected' );
}
}
}
}
}
/**
* Processes this test for T_STYLE tokens in CSS files.
*
* @since WPCS 0.11.0
*
* @param int $stackPtr The position of the current token in the stack passed in $tokens.
*
* @return void
*/
protected function process_css_style( $stackPtr ) {
if ( ! isset( $this->target_css_properties[ $this->tokens[ $stackPtr ]['content'] ] ) ) {
// Not one of the CSS properties we're interested in.
return;
}
$css_property = $this->target_css_properties[ $this->tokens[ $stackPtr ]['content'] ];
// Check if the CSS selector matches.
$opener = $this->phpcsFile->findPrevious( \T_OPEN_CURLY_BRACKET, $stackPtr );
if ( false !== $opener ) {
for ( $i = ( $opener - 1 ); $i >= 0; $i-- ) {
if ( isset( Tokens::$commentTokens[ $this->tokens[ $i ]['code'] ] )
|| \T_CLOSE_CURLY_BRACKET === $this->tokens[ $i ]['code']
) {
break;
}
}
$start = ( $i + 1 );
$selector = trim( $this->phpcsFile->getTokensAsString( $start, ( $opener - $start ) ) );
unset( $i );
foreach ( $this->target_css_selectors as $target_selector ) {
if ( false !== strpos( $selector, $target_selector ) ) {
$error = true;
if ( true === $this->remove_only ) {
// Check the value of the CSS property.
$valuePtr = $this->phpcsFile->findNext( [ \T_COLON, \T_WHITESPACE ], ( $stackPtr + 1 ), null, true );
$value = $this->tokens[ $valuePtr ]['content'];
$valid = $this->validate_css_property_value( $value, $css_property['type'], $css_property['value'] );
if ( true === $valid ) {
$error = false;
}
}
if ( true === $error ) {
$this->phpcsFile->addError( 'Hiding of the admin bar is not allowed.', $stackPtr, 'HidingDetected' );
}
}
}
}
}
/**
* Verify if a CSS property value complies with an expected value.
*
* {@internal This is a method stub, doing only what is needed for this sniff.
* If at some point in the future other sniff would need similar functionality,
* this method should be moved to the WordPress_Sniff class and expanded to cover
* all types of comparisons.}}
*
* @since WPCS 0.11.0
*
* @param mixed $value The value of CSS property.
* @param string $compare_type The type of comparison to use for the validation.
* @param string $compare_value The value to compare against.
*
* @return bool True if the property value complies, false otherwise.
*/
protected function validate_css_property_value( $value, $compare_type, $compare_value ) {
switch ( $compare_type ) {
case '!=':
return $value !== $compare_value;
case '>':
return $value > $compare_value;
default:
return false;
}
}
}
@@ -0,0 +1,75 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\PluginTerritory;
use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;
/**
* Restricts the use of various functions that are plugin territory.
*
* @link https://make.wordpress.org/themes/handbook/review/required/#presentation-vs-functionality
*
* @since 0.1.0
* @since 0.2.0 Added the `editor-blocks` group.
* Added the `cron-functionality` group.
*/
class ForbiddenFunctionsSniff extends AbstractFunctionRestrictionsSniff {
/**
* Groups of functions to restrict.
*
* Example: groups => [
* 'lambda' => [
* 'type' => 'error' | 'warning',
* 'message' => 'Use anonymous functions instead please!',
* 'functions' => [ 'file_get_contents', 'create_function' ],
* ]
* ]
*
* @return array
*/
public function getGroups() {
return [
'plugin-territory' => [
'type' => 'error',
'message' => 'Function %s() is not allowed because it is plugin territory.',
'functions' => [
'register_post_type',
'register_taxonomy',
'add_shortcode',
'register_taxonomy_for_object_type',
'flush_rewrite_rules',
],
],
'editor-blocks' => [
'type' => 'error',
'message' => 'Registering and deregistering editor blocks should be done in a plugin, not in a theme. Found %s().',
'functions' => [
'register_block_*',
'unregister_block_*',
],
],
'cron-functionality' => [
'type' => 'error',
'message' => 'Themes should not be running regular (Cron) tasks. Found %s().',
'functions' => [
'wp_clear_scheduled_hook',
'wp_cron',
'wp_reschedule_event',
'wp_schedule_*',
'wp_unschedule_*',
],
],
];
}
}
@@ -0,0 +1,66 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\PluginTerritory;
use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;
/**
* Forbids the use of add_..._page() functions within Themes with the exception of `add_theme_page()`.
*
* @link https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#admin-menu
*
* @since 0.1.0
*/
class NoAddAdminPagesSniff extends AbstractFunctionRestrictionsSniff {
/**
* Groups of functions to restrict.
*
* Example: groups => [
* 'lambda' => [
* 'type' => 'error' | 'warning',
* 'message' => 'Use anonymous functions instead please!',
* 'functions' => [ 'file_get_contents', 'create_function' ],
* ]
* ]
*
* @return array
*/
public function getGroups() {
return [
'add_menu_pages' => [
'type' => 'error',
'message' => 'Themes should use add_theme_page() for adding admin pages. Found %s.',
'functions' => [
// Menu Pages.
'add_menu_page',
'add_object_page',
'add_utility_page',
// SubMenu Pages.
'add_submenu_page',
// WordPress Administration Menus.
'add_dashboard_page',
'add_posts_page',
'add_links_page',
'add_media_page',
'add_pages_page',
'add_comments_page',
'add_plugins_page',
'add_users_page',
'add_management_page',
'add_options_page',
],
],
];
}
}
@@ -0,0 +1,79 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\PluginTerritory;
use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;
/**
* Discourages the use of session functions.
*
* @link https://make.wordpress.org/themes/handbook/review/...... @todo
*
* @since WPCS 0.3.0
* @since WPCS 0.11.0 Extends the WPCS native `AbstractFunctionRestrictionsSniff` class
* instead of the upstream `Generic.PHP.ForbiddenFunctions` sniff.
* @since WPCS 0.13.0 Class name changed: this class is now namespaced.
*
* @since TRTCS 0.1.0 As this sniff will be removed from WPCS in version 2.0, the
* sniff has been cherry-picked into the WPThemeReview standard.
*/
class SessionFunctionsUsageSniff extends AbstractFunctionRestrictionsSniff {
/**
* Groups of functions to restrict.
*
* Example: groups => [
* 'lambda' => [
* 'type' => 'error' | 'warning',
* 'message' => 'Use anonymous functions instead please!',
* 'functions' => [ 'file_get_contents', 'create_function' ],
* ]
* ]
*
* @return array
*/
public function getGroups() {
return [
'session' => [
'type' => 'error',
'message' => 'The use of PHP session function %s() is prohibited.',
'functions' => [
'session_abort',
'session_cache_expire',
'session_cache_limiter',
'session_commit',
'session_create_id',
'session_decode',
'session_destroy',
'session_encode',
'session_gc',
'session_get_cookie_params',
'session_id',
'session_is_registered',
'session_module_name',
'session_name',
'session_regenerate_id',
'session_register_shutdown',
'session_register',
'session_reset',
'session_save_path',
'session_set_cookie_params',
'session_set_save_handler',
'session_start',
'session_status',
'session_unregister',
'session_unset',
'session_write_close',
],
],
];
}
}
@@ -0,0 +1,66 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\PluginTerritory;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
/**
* Discourages the use of the session variable.
* Creating a session writes a file to the server and is unreliable in a multi-server environment.
*
* @link https://make.wordpress.org/themes/handbook/review/...... @todo
*
* @since WPCS 0.3.0
* @since WPCS 0.10.0 The sniff no longer needlessly extends the Generic_Sniffs_PHP_ForbiddenFunctionsSniff
* which it didn't use.
* @since WPCS 0.12.0 This class now extends WordPress_Sniff.
* @since WPCS 0.13.0 Class name changed: this class is now namespaced.
*
* @since TRTCS 0.1.0 As this sniff will be removed from WPCS in version 2.0, the
* sniff has been cherry-picked into the WPThemeReview standard.
*/
class SessionVariableUsageSniff implements Sniff {
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return [
\T_VARIABLE,
];
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
if ( '$_SESSION' === $tokens[ $stackPtr ]['content'] ) {
$phpcsFile->addError(
'Usage of $_SESSION variable is prohibited.',
$stackPtr,
'SessionVarsProhibited'
);
}
}
}
@@ -0,0 +1,546 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\Plugins;
use WordPressCS\WordPress\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* WordPress_Sniffs_Theme_CorrectTGMPAVersionSniff.
*
* Verifies that if the TGM Plugin Activation library is included, the correct version is used.
* - Check whether the version included is up to date.
* - Check whether the version included is downloaded via the Custom Generator with
* the correct settings.
* - Check against a persistent manual search & replace error made by theme authors.
*
* @link https://make.wordpress.org/themes/handbook/review/...... @todo
*
* @since 0.1.0
*
* {@internal This sniff currently has not (yet) been detached from the WordPressCS\WordPress\Sniff class
* as the `detect_manual_editing()` method uses the WPCS `get_function_call_parameter()` and
* `strip_quotes()` methods.
* If/when WPCS re-organizes the generic methods into traits as is being discussed in
* {@link https://github.com/WordPress/WordPress-Coding-Standards/issues/1465},
* this sniff could be decoupled and use the trait(s) instead.}}
*/
class CorrectTGMPAVersionSniff extends Sniff {
const GITHUB_TGMPA_API_URL = 'https://api.github.com/repos/TGMPA/TGM-Plugin-Activation/releases/latest';
const GITHUB_API_OAUTH_QUERY = '?access_token=%s';
/**
* TGMPA flavour required.
*
* By default, any flavour is ok. If this property is set in a custom ruleset an additional
* check will be run to verify if the correct flavour of TGMPA is used.
*
* Note: The Custom Generator only has flavours for themes, so this property is only
* relevant when checking a theme.
*
* Valid values:
* - 'wporg'
* - 'themeforest'
*
* @var string
*/
public $required_flavour = '';
/**
* GitHub oAuth token.
*
* Intended to be set in the ruleset.
*
* This is to prevent issues with rate limiting if a lot of requests are made from the same server.
*
* "Normal" users generaly won't need to set this, but if the sniffs are run for all themes
* uploaded to wordpress.org, that IP address might run into the rate limit of 60 calls per hour.
* Setting a oauth token in the custom ruleset used will prevent this.
*
* Alternatively, the token can also be set via an environment key called `GITHUB_OAUTH_TOKEN`.
*
* @var string
*/
public $github_oauth_token = '';
/**
* Fall-back for the latest version number for if the API call fails.
*
* @var string
*/
private $current_version = '2.6.1';
/**
* Whether or not a call has been made to the GitHub API to retrieve the current TGMPA version number.
*
* Note: an API call will only be made once per PHPCS run and only when the TGMPA library
* has been positively identified.
* The API call will be skipped when running the unit tests.
*
* @var bool
*/
private $gh_call_made = false;
/**
* Classes and functions declared by TGMPA.
*
* @var array
*/
private $tgmpa_classes_functions = [
// Classes.
'TGM_Plugin_Activation' => true,
'TGMPA_List_Table' => true,
'TGM_Bulk_Installer' => true,
'TGM_Bulk_Installer_Skin' => true,
'TGMPA_Bulk_Installer' => true,
'TGMPA_Bulk_Installer_Skin' => true,
'TGMPA_Utils' => true,
// Functions.
'load_tgm_plugin_activation' => true,
'tgmpa_initialize' => true, // New in v 2.6.2.
'tgmpa' => true,
'tgmpa_load_bulk_installer' => true,
];
/**
* List of available TGMPA flavours - other than the default.
*
* Used to validate `$required_flavour` property.
*
* Keys are the allowed values for the flavour property.
* Value is how this is expanded in the @version tag used by TGMPA.
*
* @var array
*/
private $valid_flavours = [
'wporg' => 'WordPress.org',
'themeforest' => 'ThemeForest',
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return [
T_OPEN_TAG,
];
}
/**
* 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 ) {
$has_class_function = $this->phpcsFile->findNext( [ T_CLASS, T_FUNCTION ], ( $stackPtr + 1 ) );
if ( false === $has_class_function ) {
// No class, function or constant declaration found, definitely not TGMPA file.
// Skip this file from further checks.
return ( $this->phpcsFile->numTokens + 1 );
}
$is_tgmpa = false;
// First check based on filename.
$file_name = basename( $this->phpcsFile->getFileName() );
if ( false !== $file_name ) {
$file_name = strtolower( $file_name );
if ( strpos( $file_name, 'class-tgm-plugin-activation.php' ) !== false ) {
$is_tgmpa = true;
} elseif ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) && preg_match( '`class-tgm-plugin-activation[^.]+\.inc`', $file_name, $discard ) > 0 ) {
$is_tgmpa = true;
}
}
/*
* Otherwise, check whether any of the TGMPA classes or function names are encountered.
*
* Will detect TGMPA, even when:
* - the class and function prefix has been changed
* - the file has been split up into several files
* - the file is combined with other code
*/
if ( false === $is_tgmpa ) {
while ( false !== $has_class_function ) {
$name = $this->phpcsFile->getDeclarationName( $has_class_function );
if ( ! empty( $name ) ) {
if ( isset( $this->tgmpa_classes_functions[ $name ] ) ) {
$is_tgmpa = true;
break;
} elseif ( strpos( $name, '_Plugin_Activation' ) !== false ) {
// This may be TGMPA with renamed prefixes, so look for typical class comment.
$prev = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $has_class_function - 1 ), null, true );
while ( false !== $prev && isset( Tokens::$commentTokens[ $this->tokens[ $prev ]['code'] ] ) ) {
if ( ( T_COMMENT === $this->tokens[ $prev ]['code']
|| T_DOC_COMMENT_STRING === $this->tokens[ $prev ]['code'] )
&& ( strpos( $this->tokens[ $prev ]['content'], 'Automatic plugin installation and activation library' ) !== false
|| strpos( $this->tokens[ $prev ]['content'], 'Automatic plugin installation and activation class' ) !== false )
) {
$is_tgmpa = true;
break;
}
$prev = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $prev - 1 ), null, true );
}
}
}
$start = ( $has_class_function + 1 );
if ( isset( $this->tokens[ $has_class_function ]['scope_condition'], $this->tokens[ $stackPtr ]['scope_closer'] )
&& $this->tokens[ $has_class_function ]['scope_condition'] === $has_class_function
) {
// Skip past anything within the class or function.
$start = $this->tokens[ $stackPtr ]['scope_closer'];
}
$has_class_function = $this->phpcsFile->findNext( [ T_CLASS, T_FUNCTION ], $start );
}
}
// If we're still not 100% sure this is TGMPA, exclude the file from further checks.
if ( false === $is_tgmpa ) {
return ( $this->phpcsFile->numTokens + 1 );
}
// Check whether the correct version of TGMPA is being used.
$version = $this->uses_latest_version();
// Check for typical manual search & replace error.
$this->detect_manual_editing( $version );
// No need to check the same file again.
return ( $this->phpcsFile->numTokens + 1 );
}
/**
* Check whether the latest version of TGMPA is being used.
*/
protected function uses_latest_version() {
if ( false === $this->gh_call_made ) {
// Get the current version number for TGMPA from GitHub.
$this->update_current_version();
$this->gh_call_made = true;
}
/*
* Walk the doc block comments to find if this is the correct version of TGMPA.
* Normally this will be in the first doc block encountered, so this is not as 'heavy' as it looks.
*/
$next_doc_block = 0;
$version = false;
$pcre_esc_flavours = array_map(
'preg_quote',
$this->valid_flavours,
array_fill( 0, count( $this->valid_flavours ), '`' )
);
$pcre_flavours = implode( '|', $pcre_esc_flavours );
do {
$next_doc_block = $this->phpcsFile->findNext( T_DOC_COMMENT_OPEN_TAG, ( $next_doc_block + 1 ) );
if ( false === $next_doc_block ) {
break;
}
$tags = $this->get_docblock_tags( $next_doc_block );
if ( empty( $tags ) ) {
continue;
}
if ( ! isset( $tags['package'], $tags['version'] ) || 'TGM-Plugin-Activation' !== $tags['package'] ) {
continue;
}
if ( preg_match( '`^([0-9\.]+(?:-(?:alpha|beta|RC)(?:[0-9])?)?)`', $tags['version'], $matches ) > 0 ) {
$version = $matches[1];
$this->phpcsFile->recordMetric( 0, 'Version', $version );
if ( true === version_compare( $this->current_version, $version, '>' ) ) {
$error = 'Upgrade of the included TGM plugin activation library required. Current version: %s. Found version: %s';
$data = [
$this->current_version,
$version,
];
$this->phpcsFile->addError( $error, 0, 'upgradeRequired', $data );
} elseif ( true === version_compare( $this->current_version, $version, '<' ) ) {
$error = 'Non-stable version of the TGM plugin activation library found. The current version is %s. Found version: %s';
$data = [
$this->current_version,
$version,
];
$this->phpcsFile->addError( $error, 0, 'unstableVersion', $data );
}
unset( $matches, $error, $data );
if ( strpos( $tags['version'], 'for parent theme' ) !== false ) {
$this->phpcsFile->recordMetric( 0, 'Used in', 'parent theme' );
} elseif ( strpos( $tags['version'], 'for child theme' ) !== false ) {
$this->phpcsFile->recordMetric( 0, 'Used in', 'child theme' );
} else {
$this->phpcsFile->recordMetric( 0, 'Used in', 'unknown' );
}
if ( preg_match( '`for publication on (' . $pcre_flavours . ')`i', $tags['version'], $flavour_match ) > 0 ) {
$this->phpcsFile->recordMetric( 0, 'Publication Channel', $flavour_match[1] );
} else {
$this->phpcsFile->recordMetric( 0, 'Publication Channel', 'n/a' );
}
if ( ! empty( $this->required_flavour ) && isset( $this->valid_flavours[ $this->required_flavour ] ) ) {
$flavour_phrase = sprintf( 'for publication on %s', $this->valid_flavours[ $this->required_flavour ] );
if ( strpos( $tags['version'], $flavour_phrase ) === false ) {
$warning = 'You are required to use a version of the TGM Plugin Activation library downloaded through the Custom TGMPA Generator. Download a fresh copy and make sure you select "%s" as your publication channel to get the correct version. http://tgmpluginactivation.com/download/';
$this->phpcsFile->addWarning(
$warning,
0,
'wrongVersion',
[ $this->valid_flavours[ $this->required_flavour ] ]
);
}
}
}
break;
} while ( false !== $next_doc_block );
// The file was recognized as TGMPA, but no valid file doc block for TGMPA was found.
if ( false === $version ) {
$this->phpcsFile->recordMetric( 0, 'Version', 'unknown' );
$error = 'The TGM Plugin Activation library was detected, but the version could not be determined. Ensure you use the latest stable release of the TGM Plugin Activation library (%s). Download a fresh copy now using the Custom TGMPA Generator at http://tgmpluginactivation.com/download/';
$data = [ $this->current_version ];
$this->phpcsFile->addError( $error, 0, 'versionUndetermined', $data );
$has_error = true;
}
return $version;
}
/**
* Check against a typical manual search & replace error often encountered.
*
* In that case the `tgmpa` in `if ( ! function_exists( 'tgmpa' ) )` has been replaced
* with the theme slug causing fatal errors when an end-user also uses a plugin using TGMPA.
*
* @param string $version The version of TGMPA found.
*/
protected function detect_manual_editing( $version ) {
// Skip this check for TGMPA versions which didn't have the `tgmpa()` function or
// didn't have the `function_exists()` wrapper.
if ( false === $version || true === version_compare( $version, '2.2.0', '<' ) ) {
return;
}
$checkTokens = [
// This is what we're looking for.
T_FUNCTION => true,
// These are just here to be able to skip as much as we can.
T_CLASS => true,
T_ARRAY => true,
T_OPEN_SHORT_ARRAY => true,
];
for ( $ptr = 0; $ptr < $this->phpcsFile->numTokens; $ptr++ ) {
if ( ! isset( $checkTokens[ $this->tokens[ $ptr ]['code'] ] ) ) {
continue;
}
// Skip as much as we can.
if ( T_CLASS === $this->tokens[ $ptr ]['code'] && isset( $this->tokens[ $ptr ]['scope_closer'] ) ) {
$ptr = $this->tokens[ $ptr ]['scope_closer'];
continue;
} elseif ( T_OPEN_SHORT_ARRAY === $this->tokens[ $ptr ]['code'] && isset( $this->tokens[ $ptr ]['bracket_closer'] ) ) {
$ptr = $this->tokens[ $ptr ]['bracket_closer'];
continue;
} elseif ( T_ARRAY === $this->tokens[ $ptr ]['code'] && isset( $this->tokens[ $ptr ]['parenthesis_closer'] ) ) {
$ptr = $this->tokens[ $ptr ]['parenthesis_closer'];
continue;
}
// Detect whether this is the `tgmpa()` function declaration.
if ( T_FUNCTION === $this->tokens[ $ptr ]['code'] ) {
$function_name = $this->phpcsFile->getDeclarationName( $ptr );
if ( 'tgmpa' !== $function_name ) {
if ( isset( $this->tokens[ $ptr ]['scope_closer'] ) ) {
// Skip the rest of the function.
$ptr = $this->tokens[ $ptr ]['scope_closer'];
}
continue;
}
// Ok, found the tgmpa function declaration. Now let's check for the typical
// manual text-domain replacement error.
$function_exists = $this->phpcsFile->findPrevious( T_STRING, ( $ptr - 1 ), null, false, 'function_exists' );
$param = false;
if ( false !== $function_exists ) {
$param = $this->get_function_call_parameter( $function_exists, 1 );
$param = $this->strip_quotes( $param['raw'] );
}
if ( false === $function_exists || 'tgmpa' !== $param ) {
$this->phpcsFile->recordMetric( 0, 'Manual editing detected', 'yes' );
$this->phpcsFile->addError(
'Manual editing of the TGM Plugin Activation file detected. Your edit will cause fatal errors for end-users. Download an official copy using the Custom TGMPA Generator. http://tgmpluginactivation.com/download/',
0,
'ManualEditDetected',
[],
9
);
} else {
$this->phpcsFile->recordMetric( 0, 'Manual editing detected', 'no' );
}
break;
}
}
}
/**
* Retrieve an array with the doc block tags from a T_DOC_COMMENT_OPEN_TAG.
*
* @param int $comment_opener The position of the comment opener.
*
* @return array
*/
protected function get_docblock_tags( $comment_opener ) {
$tags = [];
$opener = $this->tokens[ $comment_opener ];
if ( ! isset( $opener['comment_tags'] ) ) {
return $tags;
}
$closer = null;
if ( isset( $opener['comment_closer'] ) ) {
$closer = $opener['comment_closer'];
}
$tag_count = count( $opener['comment_tags'] );
for ( $i = 0; $i < $tag_count; $i++ ) {
$tag_token = $opener['comment_tags'][ $i ];
$tag = trim( $this->tokens[ $tag_token ]['content'], '@' );
$search_end = $closer;
if ( ( $i + 1 ) < $tag_count ) {
$search_end = $opener['comment_tags'][ ( $i + 1 ) ];
}
$value_token = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, ( $tag_token + 1 ), $search_end );
$tags[ $tag ] = trim( $this->tokens[ $value_token ]['content'] );
unset( $tag_token, $tag, $search_end, $value );
}
return $tags;
}
/**
* Get the version number (tag_name) of the latest TGMPA release from the GitHub API.
*/
protected function update_current_version() {
if ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) || true === $this->gh_call_made ) {
return;
}
$api_url = self::GITHUB_TGMPA_API_URL;
$oauth_token = false;
if ( '' !== $this->github_oauth_token && is_string( $this->github_oauth_token ) ) {
$oauth_token = $this->github_oauth_token;
} elseif ( false !== getenv( 'GITHUB_OAUTH_TOKEN' ) ) {
$oauth_token = getenv( 'GITHUB_OAUTH_TOKEN' );
}
if ( false !== $oauth_token ) {
$api_url .= sprintf( self::GITHUB_API_OAUTH_QUERY, $oauth_token );
}
$stream_options = [
'http' => [
'method' => 'GET',
'user_agent' => 'WordPress-Coding-Standards/Theme-Review-Sniffs',
'protocol_version' => 1.1,
],
];
$stream_context = stream_context_create( $stream_options );
$response = file_get_contents( $api_url, false, $stream_context );
$headers = $this->parse_response_headers( $http_response_header );
// Check for invalid oAuth token response.
if ( 401 === $headers['response_code'] && false !== $oauth_token ) {
$this->phpcsFile->addWarning(
'The GITHUB_OAUTH_TOKEN you provided is invalid. Please update the token in your custom ruleset or environment properties.',
0,
'githubOauthTokenInvalid'
);
$this->oauth_error = false;
return;
}
// Check for rate limit error response.
if ( 403 === $headers['response_code'] && '0' === $headers['X-RateLimit-Remaining'] ) {
// @todo Add link to GH wiki page documenting the properties.
$this->phpcsFile->addWarning(
'You are running PHPCS more than 60 times per hour. You may want to consider setting the `github_oauth_token` property in your custom ruleset for Theme Review. For more information see: ... (GH wiki page).',
0,
'githubRateLimitReached'
);
$this->rate_limit_error = false;
return;
}
if ( 200 !== $headers['response_code'] ) {
// Something unexpected going on, just ignore it.
return;
}
// Ok, we have received a valid response.
$response = json_decode( $response );
if ( ! empty( $response->tag_name ) && ( ! isset( $response->prerelease ) || false === $response->prerelease ) ) {
// Should there be a check for `v` at the start of a version number ?
$this->current_version = $response->tag_name;
}
}
/**
* Parse HTTP response headers array to a more usable format.
*
* Based on http://php.net/manual/en/reserved.variables.httpresponseheader.php#117203
*
* @param array $headers HTTP response headers array.
*
* @return array
*/
private function parse_response_headers( $headers ) {
$head = [];
foreach ( $headers as $key => $value ) {
$tag = explode( ':', $value, 2 );
if ( isset( $tag[1] ) ) {
$head[ trim( $tag[0] ) ] = trim( $tag[1] );
} else {
$head[] = $value;
if ( preg_match( '`HTTP/[0-9\.]+\s+([0-9]+)`', $value, $out ) ) {
$head['response_code'] = intval( $out[1] );
}
}
}
return $head;
}
}
@@ -0,0 +1,150 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\Privacy;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Detect the use of shortened URLs.
*
* Detection is based on a list of banned URL shortener services.
*
* @link https://make.wordpress.org/themes/handbook/review/required/#privacy
*
* @since 0.2.0
*/
class ShortenedURLsSniff implements Sniff {
/**
* Error message template.
*
* @since 0.2.0
*
* @var string
*/
const ERROR_MSG = 'Shortened URLs are not allowed in the theme. Found: "%s".';
/**
* Regex template.
*
* Will be parsed together with the url_shorteners blacklist in the register() method.
*
* @since 0.2.0
*
* @var string
*/
const REGEX_TEMPLATE = '`(?:%s)/[^\s\'"]+`i';
/**
* Supported Tokenizers.
*
* @since 0.2.0
*
* @var array
*/
public $supportedTokenizers = [
'PHP',
'CSS',
'JS',
];
/**
* Regex pattern.
*
* @since 0.2.0
*
* @var string
*/
private $regex = '';
/**
* List of url shorteners.
*
* @since 0.2.0
*
* @var array
*/
protected $url_shorteners = [
'bit.do',
'bit.ly',
'df.ly',
'goo.gl',
'is.gd',
'lc.chat',
'ow.ly',
'polr.me',
's2r.co',
'soo.gd',
'tiny.cc',
'tinyurl.com',
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 0.2.0
*
* @return array
*/
public function register() {
// Create the regex only once.
$urls = array_map(
'preg_quote',
$this->url_shorteners,
array_fill( 0, count( $this->url_shorteners ), '`' )
);
$this->regex = sprintf(
self::REGEX_TEMPLATE,
implode( '|', $urls )
);
return Tokens::$textStringTokens + [
T_COMMENT,
T_DOC_COMMENT_STRING,
T_DOC_COMMENT,
];
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 0.2.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$content = $tokens[ $stackPtr ]['content'];
if ( stripos( $content, '.' ) === false ) {
return;
}
if ( preg_match_all( $this->regex, $content, $matches ) > 0 ) {
foreach ( $matches[0] as $matched_url ) {
$phpcsFile->addError(
self::ERROR_MSG,
$stackPtr,
'Found',
[ $matched_url ]
);
}
}
}
}
@@ -0,0 +1,170 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\Templates;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
/**
* Check if the template file is using a prefix which would cause WP to interpret it as a specialised template
* meant to apply to only one page on the site.
*
* You can check the documentation of each of the functions used in determining the template hierarchy.
*
* @link https://developer.wordpress.org/reference/functions/get_category_template
* @link https://developer.wordpress.org/reference/functions/get_author_template
* @link https://developer.wordpress.org/reference/functions/get_page_template
* @link https://developer.wordpress.org/reference/functions/get_tag_template
*
* Or you can check the in depth documentation about the template hierarchy.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy
*
* @since 0.2.0
*/
class ReservedFileNamePrefixSniff implements Sniff {
/**
* Error message template.
*
* @since 0.2.0
*
* @var string
*/
const ERROR_MSG = 'Template files should not be slug specific. The file name used for this template will be interpreted by WP as %1$s-%2$s and only applied when a %1$s with the slug "%2$s" is loaded.';
/**
* Found prefix in a file.
*
* @since 0.2.0
*
* @var string
*/
protected $prefix;
/**
* File slug.
*
* @since 0.2.0
*
* @var string
*/
protected $slug;
/**
* List of reserved template file names.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
* @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
* @link https://wphierarchy.com/
* @link https://en.wikipedia.org/wiki/Media_type#Naming
*
* @since 0.2.0
*
* @var array
*/
protected $reserved_file_name_prefixes = [
'author' => true,
'category' => true,
'page' => true,
'tag' => true,
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 0.2.0
*
* @return array
*/
public function register() {
return [
\T_OPEN_TAG,
\T_OPEN_TAG_WITH_ECHO,
];
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 0.2.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return int StackPtr to the end of the file, this sniff needs to only
* check each file once.
*/
public function process( File $phpcsFile, $stackPtr ) {
// Usage of `strip_quotes` is to ensure `stdin_path` passed by IDEs does not include quotes.
$file = $this->strip_quotes( $phpcsFile->getFileName() );
$fileName = basename( $file );
$fileName = str_replace( [ '.inc', '.php' ], '', $fileName );
// Check if the current file has a prefix in the reserved list.
if ( ! $this->is_reserved_template_name_used( $fileName ) ) {
return ( $phpcsFile->numTokens + 1 );
}
$phpcsFile->addError(
self::ERROR_MSG,
0,
'ReservedTemplatePrefixFound',
[ $this->prefix, $this->slug ]
);
return ( $phpcsFile->numTokens + 1 );
}
/**
* Checks if the given file name starts with one of the reserved prefixes.
*
* @since 0.2.0
*
* @param string $file File name to check.
* @return boolean
*/
private function is_reserved_template_name_used( $file ) {
$file_parts = explode( '-', $file, 2 );
if ( empty( $file_parts[0] ) || empty( $file_parts[1] ) ) {
// No dash or dash at start or end of filename, i.e. `-something.php`.
return false;
}
if ( isset( $this->reserved_file_name_prefixes[ strtolower( $file_parts[0] ) ] ) ) {
$this->prefix = $file_parts[0];
$this->slug = $file_parts[1];
return true;
}
return false;
}
/**
* Strip quotes surrounding an arbitrary string.
*
* Intended for use with the contents of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
*
* Copied from the WordPressCS\WordPress\Sniff abstract class.
*
* @since 0.2.0
*
* @param string $string The raw string.
* @return string String without quotes around it.
*/
private function strip_quotes( $string ) {
return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string );
}
}
@@ -0,0 +1,67 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\ThouShallNotUse;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check for use of <iframe>. Often used for malicious code.
*
* @link https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#info
*
* @since 0.1.0
*/
class ForbiddenIframeSniff implements Sniff {
/**
* The regex to catch usage of <iframe ...>.
*
* This regex will prevent matches being made on `<iframe>` without attributes.
*
* @var string
*/
const IFRAME_REGEX = '`(<iframe\s+[^>]+>)`i';
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return Tokens::$textStringTokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
if ( preg_match( self::IFRAME_REGEX, $tokens[ $stackPtr ]['content'], $matches ) > 0 ) {
$phpcsFile->addError(
'Usage of the iframe HTML element is prohibited. Found: %s',
$stackPtr,
'Found',
[ $matches[1] ]
);
}
}
}
@@ -0,0 +1,99 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WPThemeReview\Sniffs\ThouShallNotUse;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check for auto generated themes.
*
* @since 0.1.0
*/
class NoAutoGenerateSniff implements Sniff {
/**
* A list of tokenizers this sniff supports.
*
* @var array
*/
public $supportedTokenizers = [
'PHP',
'CSS',
];
/**
* A list of strings found in the generated themes.
*
* @var array
*/
protected $generated_themes = [
'artisteer', // Artisteer.
'themler', // Themler.
'lubith', // Lubith.
'templatetoaster', // TemplateToaster.
'wpthemegenerator', // WP Theme Generator.
'pinegrow', // Pinegrow.
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
$tokens = Tokens::$textStringTokens;
$tokens[] = T_STRING; // Functions named after or prefixed with the generator name.
$tokens[] = T_COMMENT;
$tokens[] = T_DOC_COMMENT_STRING;
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position of the current token
* in the stack.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$token = $tokens[ $stackPtr ];
$content = trim( strtolower( $token['content'] ) );
// No need to check an empty string.
if ( '' === $content ) {
return;
}
foreach ( $this->generated_themes as $generated_theme ) {
/*
* The check can have false positives like artisteers but chances of that happening
* in a valid theme is low.
*/
if ( false === strpos( $content, $generated_theme ) ) {
continue;
}
$phpcsFile->addError(
'Auto generated themes are not allowed in the theme directory. Found: %s',
$stackPtr,
'AutoGeneratedFound',
[ $generated_theme ]
);
}
}
}
+171
View File
@@ -0,0 +1,171 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="WPThemeReview" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/squizlabs/PHP_CodeSniffer/master/phpcs.xsd">
<!-- For more information: https://make.wordpress.org/themes/handbook/review/ -->
<description>Standards any Theme to be published on wordpress.org should comply with.</description>
<!-- Themes should be compatible with PHP 5.2 and higher. -->
<config name="testVersion" value="5.2-"/>
<rule ref="PHPCompatibilityWP">
<include-pattern>*\.(php|inc)$</include-pattern>
</rule>
<!-- No PHP short open tags allowed. -->
<!-- Covers: https://github.com/Otto42/theme-check/blob/master/checks/phpshort.php -->
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<!-- Alternative PHP open tags not allowed. -->
<rule ref="Generic.PHP.DisallowAlternativePHPTags"/>
<!-- Files which start with a PHP open tag should have no whitespace preceding it. -->
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace.StartFile">
<message>To help prevent PHP "headers already sent" errors, whitespace before the PHP open tag should be removed.</message>
</rule>
<!-- Files should omit the closing PHP tag at the end of a file. -->
<rule ref="PSR2.Files.ClosingTag.NotAllowed">
<type>warning</type>
<message>To help prevent PHP "headers already sent" errors, the PHP closing tag at the end of the file should be removed.</message>
</rule>
<!-- Mixed line endings are not allowed. -->
<!-- Covers: https://github.com/Otto42/theme-check/blob/master/checks/lineendings.php -->
<rule ref="Internal.LineEndings.Mixed">
<type>error</type>
</rule>
<!-- No minification of scripts or files unless you provide original files. -->
<!-- Covers: https://make.wordpress.org/themes/handbook/review/required/#stylesheets-and-scripts -->
<rule ref="Internal.Tokenizer.Exception">
<message>File appears to be minified and cannot be processed. The non-minified file must be included too.</message>
</rule>
<!-- No ByteOrderMark allowed - important to prevent issues with content being sent before headers. -->
<rule ref="Generic.Files.ByteOrderMark"/>
<!-- Control structures which don't do anything are not very useful. -->
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
<!-- PHP tags without anything between them is just sloppy. -->
<!-- Internal: this should be replaced with the PHPCS native version of the same sniff
`Generic.CodeAnalysis.EmptyPHPStatement`, once the minimum required PHPCS version has gone up to 3.4.0. -->
<rule ref="WordPress.CodeAnalysis.EmptyStatement"/>
<!-- No removal of the admin bar allowed -->
<!-- Covers: https://github.com/wordpress/theme-check/blob/master/checks/adminbar.php -->
<rule ref="WPThemeReview.PluginTerritory.AdminBarRemoval">
<properties>
<property name="remove_only" value="false"/>
</properties>
</rule>
<!-- Check that the I18N functions are used correctly. -->
<!-- This sniff can also check the text domain, provided it's passed to PHPCS. -->
<rule ref="WordPress.WP.I18n"/>
<!-- No hard coding of scripts and styles. Everything should be enqueued. -->
<rule ref="WordPress.WP.EnqueuedResources"/>
<!-- Prevent path disclosure when using add_theme_page(). -->
<rule ref="WordPress.Security.PluginMenuSlug"/>
<!-- Do not silence error notices. e.g. Error Control Operator @.. -->
<rule ref="Generic.PHP.NoSilencedErrors">
<properties>
<property name="error" value="true"/>
</properties>
</rule>
<!-- While most themes shouldn't query the database directly, if they do, it should be done correctly. -->
<!-- Don't use the PHP database functions and classes, use the WP abstraction layer instead. -->
<rule ref="WordPress.DB.RestrictedClasses"/>
<rule ref="WordPress.DB.RestrictedFunctions"/>
<!-- All SQL queries should be prepared as close to the time of querying the database as possible. -->
<rule ref="WordPress.DB.PreparedSQL"/>
<!-- Verify that placeholders in prepared queries are used correctly. -->
<rule ref="WordPress.DB.PreparedSQLPlaceholders"/>
<!-- Validate and/or sanitize untrusted data before entering into the database. -->
<rule ref="WordPress.Security.ValidatedSanitizedInput">
<type>warning</type>
</rule>
<!-- All untrusted data should be escaped before output. -->
<rule ref="WordPress.Security.EscapeOutput">
<type>warning</type>
</rule>
<!-- Prohibit the use of the backtick operator. -->
<rule ref="Generic.PHP.BacktickOperator"/>
<!-- Prohibit overwriting of WordPress global variables. -->
<rule ref="WordPress.WP.GlobalVariablesOverride"/>
<!-- Prohibit the use of the eval() PHP language construct. -->
<rule ref="Squiz.PHP.Eval.Discouraged">
<type>error</type>
<message>eval() is a security risk so not allowed.</message>
</rule>
<!-- Prohibit the use of the `goto` PHP language construct. -->
<rule ref="Generic.PHP.DiscourageGoto.Found">
<type>error</type>
<message>The "goto" language construct should not be used.</message>
</rule>
<!-- Check for use of deprecated WordPress classes, functions and function parameters. -->
<rule ref="WordPress.WP.DeprecatedClasses"/>
<rule ref="WordPress.WP.DeprecatedFunctions"/>
<rule ref="WordPress.WP.DeprecatedParameters"/>
<!-- Check for deprecated WordPress constants. -->
<rule ref="WordPress.WP.DiscouragedConstants">
<type>error</type>
</rule>
<!-- Check for usage of deprecated parameter values in WP functions and provide alternative based on the parameter passed. -->
<rule ref="WordPress.WP.DeprecatedParameterValues"/>
<!-- Verify that everything in the global namespace is prefixed. -->
<!-- Covers: https://make.wordpress.org/themes/handbook/review/required/#code - last bullet. -->
<!-- NOTE: this sniff needs a custom property to be set for it to be activated. -->
<!-- See: https://github.com/WordPress/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace -->
<rule ref="WPThemeReview.CoreFunctionality.PrefixAllGlobals">
<properties>
<property name="allowed_folders" type="array">
<element value="template-parts"/>
<element value="templates"/>
<element value="partials"/>
<element value="page-templates"/>
</property>
</properties>
</rule>
<!-- Check for correct spelling of WordPress. -->
<!-- Covers: https://make.wordpress.org/themes/handbook/review/required/#naming - third bullet. -->
<rule ref="WordPress.WP.CapitalPDangit">
<type>error</type>
</rule>
<!-- If TGMPA is used, verify that the version is up to date. -->
<rule ref="WPThemeReview.Plugins.CorrectTGMPAVersion">
<!-- Require that the Custom Generator is used to download & adjust TGMPA. -->
<properties>
<property name="required_flavour" value="wporg"/>
</properties>
</rule>
<!-- Themes should not use the PHP session functions nor access the $_SESSION variable. -->
<!-- Covered by the WPThemeReview.PluginTerritory.SessionFunctionsUsage and WPThemeReview.PluginTerritory.SessionVariableUsage sniffs. -->
<!-- Themes should never touch the timezone. -->
<rule ref="WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set"/>
<!-- Themes shouldn't change ini configuration during runtime. -->
<rule ref="WordPress.PHP.IniSet">
<type>error</type>
</rule>
</ruleset>
+75
View File
@@ -0,0 +1,75 @@
{
"name" : "wptrt/wpthemereview",
"description": "PHP_CodeSniffer rules (sniffs) to verify theme compliance with the rules for theme hosting on wordpress.org",
"type" : "phpcodesniffer-standard",
"keywords" : [
"phpcs",
"standards",
"WordPress",
"Themes"
],
"homepage" : "https://make.wordpress.org/themes/handbook/review/",
"license" : "MIT",
"authors" : [
{
"name" : "Theme Review Team",
"homepage": "https://make.wordpress.org/themes/handbook/",
"role" : "Strategy and rule setting"
},
{
"name" : "Ulrich Pogson",
"homepage": "https://github.com/grappler",
"role" : "Lead developer"
},
{
"name" : "Juliette Reinders Folmer",
"homepage": "https://github.com/jrfnl",
"role" : "Lead developer"
},
{
"name" : "Denis Žoljom",
"homepage": "https://github.com/dingo-d",
"role" : "Plugin integration lead"
},
{
"name" : "Contributors",
"homepage": "https://github.com/WPTRT/WPThemeReview/graphs/contributors"
}
],
"require" : {
"php" : ">=5.4",
"squizlabs/php_codesniffer": "^3.3.1",
"wp-coding-standards/wpcs" : "^2.2.0",
"phpcompatibility/phpcompatibility-wp": "^2.0"
},
"require-dev": {
"phpunit/phpunit" : "^4.0 || ^5.0 || ^6.0 || ^7.0",
"phpcompatibility/php-compatibility": "^9.0",
"roave/security-advisories" : "dev-master",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0"
},
"suggest" : {
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
},
"scripts" : {
"install-standards": [
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --config-set installed_paths ../../..,../../wp-coding-standards/wpcs,../../phpcompatibility/phpcompatibility-wp,../../phpcompatibility/phpcompatibility-paragonie"
],
"run-tests": [
"@php ./vendor/phpunit/phpunit/phpunit --filter WPThemeReview ./vendor/squizlabs/php_codesniffer/tests/AllTests.php"
],
"phpcs-i": [
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs -i"
],
"check-cs": [
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs"
],
"fix-cs": [
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf"
]
},
"support" : {
"issues": "https://github.com/WPTRT/WPThemeReview/issues",
"source": "https://github.com/WPTRT/WPThemeReview"
}
}