基础代码
This commit is contained in:
+69
@@ -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'] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+234
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+117
@@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+191
@@ -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;
|
||||
}
|
||||
}
|
||||
+417
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+75
@@ -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_*',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+79
@@ -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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+66
@@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+546
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+150
@@ -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 ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -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 );
|
||||
}
|
||||
}
|
||||
+67
@@ -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] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -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 ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user