mirror of
https://github.com/10h30/genesis-simple-sidebars.git
synced 2026-07-20 15:03:34 +09:00
Introducing coding standards validation.
This commit is contained in:
committed by
Nathan Rice
parent
6e40c921ab
commit
fd504d41ac
Vendored
+949
@@ -0,0 +1,949 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Coding Standard.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace WordPress\Sniffs\NamingConventions;
|
||||
|
||||
use WordPress\AbstractFunctionParameterSniff;
|
||||
use WordPress\PHPCSHelper;
|
||||
use PHP_CodeSniffer_Tokens as Tokens;
|
||||
|
||||
/**
|
||||
* Verify that everything defined in the global namespace is prefixed with a theme/plugin specific prefix.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.12.0
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
* @since 1.2.0 Now also checks whether namespaces are prefixed.
|
||||
*
|
||||
* @uses \WordPress\Sniff::$custom_test_class_whitelist
|
||||
*/
|
||||
class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff {
|
||||
|
||||
/**
|
||||
* Error message template.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ERROR_MSG = '%s by a theme/plugin should start with the theme/plugin prefix. Found: "%s".';
|
||||
|
||||
/**
|
||||
* Target prefixes.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var string[]|string
|
||||
*/
|
||||
public $prefixes = '';
|
||||
|
||||
/**
|
||||
* Prefix blacklist.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $prefix_blacklist = array(
|
||||
'wordpress' => true,
|
||||
'wp' => true,
|
||||
'_' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Target prefixes after validation.
|
||||
*
|
||||
* All prefixes are lowercased for case-insensitive compare.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $validated_prefixes = array();
|
||||
|
||||
/**
|
||||
* Target namespace prefixes after validation with regex indicator.
|
||||
*
|
||||
* All prefixes are lowercased for case-insensitive compare.
|
||||
* If the prefix doesn't already contain a namespace separator, but does contain
|
||||
* non-word characters, these will have been replaced with regex syntax to allow
|
||||
* for namespace separators and the `is_regex` indicator will have been set to `true`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validated_namespace_prefixes = array();
|
||||
|
||||
/**
|
||||
* Cache of previously set prefixes.
|
||||
*
|
||||
* Prevents having to do the same prefix validation over and over again.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $previous_prefixes = array();
|
||||
|
||||
/**
|
||||
* A list of all PHP superglobals with the exception of $GLOBALS which is handled separately.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $superglobals = array(
|
||||
'_COOKIE' => true,
|
||||
'_ENV' => true,
|
||||
'_GET' => true,
|
||||
'_FILES' => true,
|
||||
'_POST' => true,
|
||||
'_REQUEST' => true,
|
||||
'_SERVER' => true,
|
||||
'_SESSION' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A list of core hooks that are allowed to be called by plugins and themes.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $whitelisted_core_hooks = array(
|
||||
'widget_title' => true,
|
||||
'add_meta_boxes' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A list of core constants that are allowed to be defined by plugins and themes.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* Source: {@link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/default-constants.php#L0}
|
||||
* The constants are listed in the order they are found in the source file
|
||||
* to make life easier for future updates.
|
||||
* Only overrulable constants are listed, i.e. those defined within core within
|
||||
* a `if ( ! defined() ) {}` wrapper.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $whitelisted_core_constants = array(
|
||||
'WP_MEMORY_LIMIT' => true,
|
||||
'WP_MAX_MEMORY_LIMIT' => true,
|
||||
'WP_CONTENT_DIR' => true,
|
||||
'WP_DEBUG' => true,
|
||||
'WP_DEBUG_DISPLAY' => true,
|
||||
'WP_DEBUG_LOG' => true,
|
||||
'WP_CACHE' => true,
|
||||
'SCRIPT_DEBUG' => true,
|
||||
'MEDIA_TRASH' => true,
|
||||
'SHORTINIT' => true,
|
||||
'WP_CONTENT_URL' => true,
|
||||
'WP_PLUGIN_DIR' => true,
|
||||
'WP_PLUGIN_URL' => true,
|
||||
'PLUGINDIR' => true,
|
||||
'WPMU_PLUGIN_DIR' => true,
|
||||
'WPMU_PLUGIN_URL' => true,
|
||||
'MUPLUGINDIR' => true,
|
||||
'COOKIEHASH' => true,
|
||||
'USER_COOKIE' => true,
|
||||
'PASS_COOKIE' => true,
|
||||
'AUTH_COOKIE' => true,
|
||||
'SECURE_AUTH_COOKIE' => true,
|
||||
'LOGGED_IN_COOKIE' => true,
|
||||
'TEST_COOKIE' => true,
|
||||
'COOKIEPATH' => true,
|
||||
'SITECOOKIEPATH' => true,
|
||||
'ADMIN_COOKIE_PATH' => true,
|
||||
'PLUGINS_COOKIE_PATH' => true,
|
||||
'COOKIE_DOMAIN' => true,
|
||||
'FORCE_SSL_ADMIN' => true,
|
||||
'FORCE_SSL_LOGIN' => true,
|
||||
'AUTOSAVE_INTERVAL' => true,
|
||||
'EMPTY_TRASH_DAYS' => true,
|
||||
'WP_POST_REVISIONS' => true,
|
||||
'WP_CRON_LOCK_TIMEOUT' => true,
|
||||
'WP_DEFAULT_THEME' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
$targets = array(
|
||||
\T_NAMESPACE => \T_NAMESPACE,
|
||||
\T_FUNCTION => \T_FUNCTION,
|
||||
\T_CLASS => \T_CLASS,
|
||||
\T_INTERFACE => \T_INTERFACE,
|
||||
\T_TRAIT => \T_TRAIT,
|
||||
\T_CONST => \T_CONST,
|
||||
\T_VARIABLE => \T_VARIABLE,
|
||||
\T_DOLLAR => \T_DOLLAR, // Variable variables.
|
||||
\T_ANON_CLASS => \T_ANON_CLASS, // Only used for skipping over test classes.
|
||||
);
|
||||
|
||||
// Add function call target for hook names and constants defined using define().
|
||||
$parent = parent::register();
|
||||
if ( ! empty( $parent ) ) {
|
||||
$targets[] = \T_STRING;
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups of functions to restrict.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups() {
|
||||
$this->target_functions = $this->hookInvokeFunctions;
|
||||
$this->target_functions['define'] = true;
|
||||
|
||||
return parent::getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes this test, when one of its tokens is encountered.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return int|void Integer stack pointer to skip forward or void to continue
|
||||
* normal file processing.
|
||||
*/
|
||||
public function process_token( $stackPtr ) {
|
||||
/*
|
||||
* Allow for whitelisting.
|
||||
*
|
||||
* Generally speaking a theme/plugin should *only* execute their own hooks, but there may be a
|
||||
* good reason to execute a core hook.
|
||||
*
|
||||
* Similarly, newer PHP or WP functions or constants may need to be emulated for continued support
|
||||
* of older PHP and WP versions.
|
||||
*/
|
||||
if ( $this->has_whitelist_comment( 'prefix', $stackPtr ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow overruling the prefixes set in a ruleset via the command line.
|
||||
$cl_prefixes = trim( PHPCSHelper::get_config_data( 'prefixes' ) );
|
||||
if ( ! empty( $cl_prefixes ) ) {
|
||||
$this->prefixes = $cl_prefixes;
|
||||
}
|
||||
|
||||
$this->prefixes = $this->merge_custom_array( $this->prefixes, array(), false );
|
||||
if ( empty( $this->prefixes ) ) {
|
||||
// No prefixes passed, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate_prefixes();
|
||||
if ( empty( $this->validated_prefixes ) ) {
|
||||
// No _valid_ prefixes passed, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore test classes.
|
||||
if ( ( \T_CLASS === $this->tokens[ $stackPtr ]['code']
|
||||
|| \T_TRAIT === $this->tokens[ $stackPtr ]['code']
|
||||
|| \T_ANON_CLASS === $this->tokens[ $stackPtr ]['code'] )
|
||||
&& true === $this->is_test_class( $stackPtr )
|
||||
) {
|
||||
if ( $this->tokens[ $stackPtr ]['scope_condition'] === $stackPtr && isset( $this->tokens[ $stackPtr ]['scope_closer'] ) ) {
|
||||
// Skip forward to end of test class.
|
||||
return $this->tokens[ $stackPtr ]['scope_closer'];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \T_ANON_CLASS === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
// Token was only registered to allow skipping over test classes.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
// Disallow excluding function groups for this sniff.
|
||||
$this->exclude = array();
|
||||
|
||||
return parent::process_token( $stackPtr );
|
||||
|
||||
} elseif ( \T_DOLLAR === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
|
||||
return $this->process_variable_variable( $stackPtr );
|
||||
|
||||
} elseif ( \T_VARIABLE === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
|
||||
return $this->process_variable_assignment( $stackPtr );
|
||||
|
||||
} elseif ( \T_NAMESPACE === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
$namespace_name = $this->get_declared_namespace_name( $stackPtr );
|
||||
|
||||
if ( false === $namespace_name || '' === $namespace_name || '\\' === $namespace_name ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->validated_namespace_prefixes as $key => $prefix_info ) {
|
||||
if ( false === $prefix_info['is_regex'] ) {
|
||||
if ( stripos( $namespace_name, $prefix_info['prefix'] ) === 0 ) {
|
||||
$this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $key );
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ok, so this prefix should be used as a regex.
|
||||
$regex = '`^' . $prefix_info['prefix'] . '`i';
|
||||
if ( preg_match( $regex, $namespace_name ) > 0 ) {
|
||||
$this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $key );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still here ? In that case, we have a non-prefixed namespace name.
|
||||
$recorded = $this->phpcsFile->addError(
|
||||
self::ERROR_MSG,
|
||||
$stackPtr,
|
||||
'NonPrefixedNamespaceFound',
|
||||
array(
|
||||
'Namespaces declared',
|
||||
$namespace_name,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $recorded ) {
|
||||
$this->record_potential_prefix_metric( $stackPtr, $namespace_name );
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
// Namespaced methods, classes and constants do not need to be prefixed.
|
||||
$namespace = $this->determine_namespace( $stackPtr );
|
||||
if ( '' !== $namespace && '\\' !== $namespace ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item_name = '';
|
||||
$error_text = 'Unknown syntax used';
|
||||
$error_code = 'NonPrefixedSyntaxFound';
|
||||
|
||||
switch ( $this->tokens[ $stackPtr ]['type'] ) {
|
||||
case 'T_FUNCTION':
|
||||
// Methods in a class do not need to be prefixed.
|
||||
if ( $this->phpcsFile->hasCondition( $stackPtr, array( \T_CLASS, \T_ANON_CLASS, \T_INTERFACE, \T_TRAIT ) ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item_name = $this->phpcsFile->getDeclarationName( $stackPtr );
|
||||
if ( function_exists( '\\' . $item_name ) ) {
|
||||
// Backfill for PHP native function.
|
||||
return;
|
||||
}
|
||||
|
||||
$error_text = 'Functions declared';
|
||||
$error_code = 'NonPrefixedFunctionFound';
|
||||
break;
|
||||
|
||||
case 'T_CLASS':
|
||||
case 'T_INTERFACE':
|
||||
case 'T_TRAIT':
|
||||
$item_name = $this->phpcsFile->getDeclarationName( $stackPtr );
|
||||
$error_text = 'Classes declared';
|
||||
$error_code = 'NonPrefixedClassFound';
|
||||
|
||||
switch ( $this->tokens[ $stackPtr ]['type'] ) {
|
||||
case 'T_CLASS':
|
||||
if ( class_exists( '\\' . $item_name, false ) ) {
|
||||
// Backfill for PHP native class.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'T_INTERFACE':
|
||||
if ( interface_exists( '\\' . $item_name, false ) ) {
|
||||
// Backfill for PHP native interface.
|
||||
return;
|
||||
}
|
||||
|
||||
$error_text = 'Interfaces declared';
|
||||
$error_code = 'NonPrefixedInterfaceFound';
|
||||
break;
|
||||
|
||||
case 'T_TRAIT':
|
||||
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.trait_existsFound
|
||||
if ( function_exists( '\trait_exists' ) && trait_exists( '\\' . $item_name, false ) ) {
|
||||
// Backfill for PHP native trait.
|
||||
return;
|
||||
}
|
||||
|
||||
$error_text = 'Traits declared';
|
||||
$error_code = 'NonPrefixedTraitFound';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Left empty on purpose.
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'T_CONST':
|
||||
// Constants in a class do not need to be prefixed.
|
||||
if ( true === $this->is_class_constant( $stackPtr ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$constant_name_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
||||
if ( false === $constant_name_ptr ) {
|
||||
// Live coding.
|
||||
return;
|
||||
}
|
||||
|
||||
$item_name = $this->tokens[ $constant_name_ptr ]['content'];
|
||||
if ( \defined( '\\' . $item_name ) ) {
|
||||
// Backfill for PHP native constant.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $this->whitelisted_core_constants[ $item_name ] ) ) {
|
||||
// Defining a WP Core constant intended for overruling.
|
||||
return;
|
||||
}
|
||||
|
||||
$error_text = 'Global constants defined';
|
||||
$error_code = 'NonPrefixedConstantFound';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Left empty on purpose.
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( empty( $item_name ) || $this->is_prefixed( $stackPtr, $item_name ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recorded = $this->phpcsFile->addError(
|
||||
self::ERROR_MSG,
|
||||
$stackPtr,
|
||||
$error_code,
|
||||
array(
|
||||
$error_text,
|
||||
$item_name,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $recorded ) {
|
||||
$this->record_potential_prefix_metric( $stackPtr, $item_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle variable variables defined in the global namespace.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return int|void Integer stack pointer to skip forward or void to continue
|
||||
* normal file processing.
|
||||
*/
|
||||
protected function process_variable_variable( $stackPtr ) {
|
||||
static $indicators = array(
|
||||
\T_OPEN_CURLY_BRACKET => true,
|
||||
\T_VARIABLE => true,
|
||||
);
|
||||
|
||||
// Is this a variable variable ?
|
||||
// Not concerned with nested ones as those will be recognized on their own token.
|
||||
$next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
||||
if ( false === $next_non_empty || ! isset( $indicators[ $this->tokens[ $next_non_empty ]['code'] ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \T_OPEN_CURLY_BRACKET === $this->tokens[ $next_non_empty ]['code']
|
||||
&& isset( $this->tokens[ $next_non_empty ]['bracket_closer'] )
|
||||
) {
|
||||
// Skip over the variable part.
|
||||
$next_non_empty = $this->tokens[ $next_non_empty ]['bracket_closer'];
|
||||
}
|
||||
|
||||
$maybe_assignment = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true, null, true );
|
||||
|
||||
while ( false !== $maybe_assignment
|
||||
&& \T_OPEN_SQUARE_BRACKET === $this->tokens[ $maybe_assignment ]['code']
|
||||
&& isset( $this->tokens[ $maybe_assignment ]['bracket_closer'] )
|
||||
) {
|
||||
$maybe_assignment = $this->phpcsFile->findNext(
|
||||
Tokens::$emptyTokens,
|
||||
( $this->tokens[ $maybe_assignment ]['bracket_closer'] + 1 ),
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ( false === $maybe_assignment ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( Tokens::$assignmentTokens[ $this->tokens[ $maybe_assignment ]['code'] ] ) ) {
|
||||
// Not an assignment.
|
||||
return;
|
||||
}
|
||||
|
||||
$error = self::ERROR_MSG;
|
||||
|
||||
/*
|
||||
* Local variable variables in a function do not need to be prefixed.
|
||||
* But a variable variable could evaluate to the name of an imported global
|
||||
* variable.
|
||||
* Not concerned with imported variable variables (global.. ) as that has been
|
||||
* forbidden since PHP 7.0. Presuming cross-version code and if not, that
|
||||
* is for the PHPCompatibility standard to detect.
|
||||
*/
|
||||
if ( $this->phpcsFile->hasCondition( $stackPtr, array( \T_FUNCTION, \T_CLOSURE ) ) === true ) {
|
||||
$condition = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );
|
||||
if ( false === $condition ) {
|
||||
$condition = $this->phpcsFile->getCondition( $stackPtr, \T_CLOSURE );
|
||||
}
|
||||
|
||||
$has_global = $this->phpcsFile->findPrevious( \T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $condition ]['scope_opener'] );
|
||||
if ( false === $has_global ) {
|
||||
// No variable import happening.
|
||||
return;
|
||||
}
|
||||
|
||||
$error = 'Variable variable which could potentially override an imported global variable detected. ' . $error;
|
||||
}
|
||||
|
||||
$variable_name = $this->phpcsFile->getTokensAsString( $stackPtr, ( ( $next_non_empty - $stackPtr ) + 1 ) );
|
||||
|
||||
// Still here ? In that case, the variable name should be prefixed.
|
||||
$recorded = $this->phpcsFile->addWarning(
|
||||
$error,
|
||||
$stackPtr,
|
||||
'NonPrefixedVariableFound',
|
||||
array(
|
||||
'Variables defined',
|
||||
$variable_name,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $recorded ) {
|
||||
$this->record_potential_prefix_metric( $stackPtr, $variable_name );
|
||||
}
|
||||
|
||||
// Skip over the variable part of the variable.
|
||||
return ( $next_non_empty + 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that defined global variables are prefixed.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return int|void Integer stack pointer to skip forward or void to continue
|
||||
* normal file processing.
|
||||
*/
|
||||
protected function process_variable_assignment( $stackPtr ) {
|
||||
/*
|
||||
* We're only concerned with variables which are being defined.
|
||||
* `is_assigment()` will not recognize property assignments, which is good in this case.
|
||||
* However it will also not recognize $b in `foreach( $a as $b )` as an assignment, so
|
||||
* we need a separate check for that.
|
||||
*/
|
||||
if ( false === $this->is_assignment( $stackPtr )
|
||||
&& false === $this->is_foreach_as( $stackPtr )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_error = true;
|
||||
$variable_name = substr( $this->tokens[ $stackPtr ]['content'], 1 ); // Strip the dollar sign.
|
||||
|
||||
// Bow out early if we know for certain no prefix is needed.
|
||||
if ( $this->variable_prefixed_or_whitelisted( $stackPtr, $variable_name ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'GLOBALS' === $variable_name ) {
|
||||
$array_open = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
||||
if ( false === $array_open || \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $array_open ]['code'] ) {
|
||||
// Live coding or something very silly.
|
||||
return;
|
||||
}
|
||||
|
||||
$array_key = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $array_open + 1 ), null, true, null, true );
|
||||
if ( false === $array_key ) {
|
||||
// No key found, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
$stackPtr = $array_key;
|
||||
$variable_name = $this->strip_quotes( $this->tokens[ $array_key ]['content'] );
|
||||
|
||||
// Check whether a prefix is needed.
|
||||
if ( isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] )
|
||||
&& $this->variable_prefixed_or_whitelisted( $stackPtr, $variable_name ) === true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $array_key ]['code'] ) {
|
||||
// If the array key is a double quoted string, try again with only
|
||||
// the part before the first variable (if any).
|
||||
$exploded = explode( '$', $variable_name );
|
||||
$first = rtrim( $exploded[0], '{' );
|
||||
if ( '' !== $first ) {
|
||||
if ( $this->variable_prefixed_or_whitelisted( $array_key, $first ) === true ) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If the first part was dynamic, throw a warning.
|
||||
$is_error = false;
|
||||
}
|
||||
} elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] ) ) {
|
||||
// Dynamic array key, throw a warning.
|
||||
$is_error = false;
|
||||
}
|
||||
} else {
|
||||
// Function parameters do not need to be prefixed.
|
||||
if ( isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
||||
foreach ( $this->tokens[ $stackPtr ]['nested_parenthesis'] as $opener => $closer ) {
|
||||
if ( isset( $this->tokens[ $opener ]['parenthesis_owner'] )
|
||||
&& ( \T_FUNCTION === $this->tokens[ $this->tokens[ $opener ]['parenthesis_owner'] ]['code']
|
||||
|| \T_CLOSURE === $this->tokens[ $this->tokens[ $opener ]['parenthesis_owner'] ]['code'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
unset( $opener, $closer );
|
||||
}
|
||||
|
||||
// Properties in a class do not need to be prefixed.
|
||||
if ( true === $this->is_class_property( $stackPtr ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Local variables in a function do not need to be prefixed unless they are being imported.
|
||||
if ( $this->phpcsFile->hasCondition( $stackPtr, array( \T_FUNCTION, \T_CLOSURE ) ) === true ) {
|
||||
$condition = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );
|
||||
if ( false === $condition ) {
|
||||
$condition = $this->phpcsFile->getCondition( $stackPtr, \T_CLOSURE );
|
||||
}
|
||||
|
||||
$has_global = $this->phpcsFile->findPrevious( \T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $condition ]['scope_opener'] );
|
||||
if ( false === $has_global ) {
|
||||
// No variable import happening.
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, this may be an imported global variable.
|
||||
$end_of_statement = $this->phpcsFile->findNext( \T_SEMICOLON, ( $has_global + 1 ) );
|
||||
if ( false === $end_of_statement ) {
|
||||
// No semi-colon - live coding.
|
||||
return;
|
||||
}
|
||||
|
||||
for ( $ptr = ( $has_global + 1 ); $ptr <= $end_of_statement; $ptr++ ) {
|
||||
// Move the stack pointer to the next variable.
|
||||
$ptr = $this->phpcsFile->findNext( \T_VARIABLE, $ptr, $end_of_statement, false, null, true );
|
||||
|
||||
if ( false === $ptr ) {
|
||||
// Reached the end of the global statement without finding the variable,
|
||||
// so this must be a local variable.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( substr( $this->tokens[ $ptr ]['content'], 1 ) === $variable_name ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $condition, $has_global, $end_of_statement, $ptr, $imported );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Still here ? In that case, the variable name should be prefixed.
|
||||
$recorded = $this->addMessage(
|
||||
self::ERROR_MSG,
|
||||
$stackPtr,
|
||||
$is_error,
|
||||
'NonPrefixedVariableFound',
|
||||
array(
|
||||
'Variables defined',
|
||||
'$' . $variable_name,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $recorded ) {
|
||||
$this->record_potential_prefix_metric( $stackPtr, $variable_name );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the parameters of a matched function.
|
||||
*
|
||||
* @since 0.12.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 ) {
|
||||
|
||||
// Ignore deprecated hook names.
|
||||
if ( strpos( $matched_content, '_deprecated' ) > 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No matter whether it is a constant definition or a hook call, both use the first parameter.
|
||||
if ( ! isset( $parameters[1] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_error = true;
|
||||
$raw_content = $this->strip_quotes( $parameters[1]['raw'] );
|
||||
|
||||
if ( ( 'define' !== $matched_content
|
||||
&& isset( $this->whitelisted_core_hooks[ $raw_content ] ) )
|
||||
|| ( 'define' === $matched_content
|
||||
&& isset( $this->whitelisted_core_constants[ $raw_content ] ) )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_prefixed( $parameters[1]['start'], $raw_content ) === true ) {
|
||||
return;
|
||||
} else {
|
||||
// This may be a dynamic hook/constant name.
|
||||
$first_non_empty = $this->phpcsFile->findNext(
|
||||
Tokens::$emptyTokens,
|
||||
$parameters[1]['start'],
|
||||
( $parameters[1]['end'] + 1 ),
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $first_non_empty ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$first_non_empty_content = $this->strip_quotes( $this->tokens[ $first_non_empty ]['content'] );
|
||||
|
||||
// Try again with just the first token if it's a text string.
|
||||
if ( isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] )
|
||||
&& $this->is_prefixed( $parameters[1]['start'], $first_non_empty_content ) === true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $first_non_empty ]['code'] ) {
|
||||
// If the first part of the parameter is a double quoted string, try again with only
|
||||
// the part before the first variable (if any).
|
||||
$exploded = explode( '$', $first_non_empty_content );
|
||||
$first = rtrim( $exploded[0], '{' );
|
||||
if ( '' !== $first ) {
|
||||
if ( $this->is_prefixed( $parameters[1]['start'], $first ) === true ) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Start of hook/constant name is dynamic, throw a warning.
|
||||
$is_error = false;
|
||||
}
|
||||
} elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) ) {
|
||||
// Dynamic hook/constant name, throw a warning.
|
||||
$is_error = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'define' === $matched_content ) {
|
||||
if ( \defined( '\\' . $raw_content ) ) {
|
||||
// Backfill for PHP native constant.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( strpos( $raw_content, '\\' ) !== false ) {
|
||||
// Namespaced or unreachable constant.
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array( 'Global constants defined' );
|
||||
$error_code = 'NonPrefixedConstantFound';
|
||||
} else {
|
||||
$data = array( 'Hook names invoked' );
|
||||
$error_code = 'NonPrefixedHooknameFound';
|
||||
}
|
||||
|
||||
$data[] = $raw_content;
|
||||
|
||||
$recorded = $this->addMessage( self::ERROR_MSG, $first_non_empty, $is_error, $error_code, $data );
|
||||
|
||||
if ( true === $recorded ) {
|
||||
$this->record_potential_prefix_metric( $stackPtr, $raw_content );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a function/class/constant/variable name is prefixed with one of the expected prefixes.
|
||||
*
|
||||
* @since 0.12.0
|
||||
* @since 0.14.0 Allows for other non-word characters as well as underscores to better support hook names.
|
||||
* @since 1.0.0 Does not require a word seperator anymore after a prefix.
|
||||
* This allows for improved code style independent checking,
|
||||
* i.e. allows for camelCase naming and the likes.
|
||||
* @since 1.0.1 - Added $stackPtr parameter.
|
||||
* - The function now also records metrics about the prefixes encountered.
|
||||
*
|
||||
* @param int $stackPtr The position of the token to record the metric against.
|
||||
* @param string $name Name to check for a prefix.
|
||||
*
|
||||
* @return bool True when the name is one of the prefixes or starts with an allowed prefix.
|
||||
* False otherwise.
|
||||
*/
|
||||
private function is_prefixed( $stackPtr, $name ) {
|
||||
foreach ( $this->validated_prefixes as $prefix ) {
|
||||
if ( stripos( $name, $prefix ) === 0 ) {
|
||||
$this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: allowed prefixes', $prefix );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a variable name might need a prefix.
|
||||
*
|
||||
* Prefix is not needed for:
|
||||
* - superglobals,
|
||||
* - WP native globals,
|
||||
* - variables which are already prefixed.
|
||||
*
|
||||
* @since 0.12.0
|
||||
* @since 1.0.1 Added $stackPtr parameter.
|
||||
*
|
||||
* @param int $stackPtr The position of the token to record the metric against.
|
||||
* @param string $name Variable name without the dollar sign.
|
||||
*
|
||||
* @return bool True if the variable name is whitelisted or already prefixed.
|
||||
* False otherwise.
|
||||
*/
|
||||
private function variable_prefixed_or_whitelisted( $stackPtr, $name ) {
|
||||
// Ignore superglobals and WP global variables.
|
||||
if ( isset( $this->superglobals[ $name ] ) || isset( $this->wp_globals[ $name ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_prefixed( $stackPtr, $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an array of prefixes as passed through a custom property or via the command line.
|
||||
*
|
||||
* Checks that the prefix:
|
||||
* - is not one of the blacklisted ones.
|
||||
* - complies with the PHP rules for valid function, class, variable, constant names.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*/
|
||||
private function validate_prefixes() {
|
||||
if ( $this->previous_prefixes === $this->prefixes ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the cache *before* validation so as to not break the above compare.
|
||||
$this->previous_prefixes = $this->prefixes;
|
||||
|
||||
// Validate the passed prefix(es).
|
||||
$prefixes = array();
|
||||
$ns_prefixes = array();
|
||||
foreach ( $this->prefixes as $key => $prefix ) {
|
||||
$prefixLC = strtolower( $prefix );
|
||||
|
||||
if ( isset( $this->prefix_blacklist[ $prefixLC ] ) ) {
|
||||
$this->phpcsFile->addError(
|
||||
'The "%s" prefix is not allowed.',
|
||||
0,
|
||||
'ForbiddenPrefixPassed',
|
||||
array( $prefix )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate the prefix against characters allowed for function, class, constant names etc.
|
||||
if ( preg_match( '`^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*$`', $prefix ) !== 1 ) {
|
||||
$this->phpcsFile->addWarning(
|
||||
'The "%s" prefix is not a valid namespace/function/class/variable/constant prefix in PHP.',
|
||||
0,
|
||||
'InvalidPrefixPassed',
|
||||
array( $prefix )
|
||||
);
|
||||
}
|
||||
|
||||
// Lowercase the prefix to allow for direct compare.
|
||||
$prefixes[ $key ] = $prefixLC;
|
||||
|
||||
/*
|
||||
* Replace non-word characters in the prefix with a regex snippet, but only if the
|
||||
* string doesn't already contain namespace separators.
|
||||
*/
|
||||
$is_regex = false;
|
||||
if ( strpos( $prefix, '\\' ) === false && preg_match( '`[_\W]`', $prefix ) > 0 ) {
|
||||
$prefix = preg_replace( '`([_\W])`', '[\\\\\\\\$1]', $prefixLC );
|
||||
$is_regex = true;
|
||||
}
|
||||
|
||||
$ns_prefixes[ $prefixLC ] = array(
|
||||
'prefix' => $prefix,
|
||||
'is_regex' => $is_regex,
|
||||
);
|
||||
}
|
||||
|
||||
// Set the validated prefixes caches.
|
||||
$this->validated_prefixes = $prefixes;
|
||||
$this->validated_namespace_prefixes = $ns_prefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the "potential prefix" metric.
|
||||
*
|
||||
* @since 1.0.1
|
||||
*
|
||||
* @param int $stackPtr The position of the token to record the metric against.
|
||||
* @param string $construct_name Name of the global construct to try and distill a potential prefix from.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function record_potential_prefix_metric( $stackPtr, $construct_name ) {
|
||||
if ( preg_match( '`^([A-Z]*[a-z0-9]*+)`', ltrim( $construct_name, '\$_' ), $matches ) > 0
|
||||
&& isset( $matches[1] ) && '' !== $matches[1]
|
||||
) {
|
||||
$this->phpcsFile->recordMetric( $stackPtr, 'Prefix all globals: potential prefixes - start of non-prefixed construct', strtolower( $matches[1] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Coding Standard.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace WordPress\Sniffs\NamingConventions;
|
||||
|
||||
use PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff as PHPCS_PEAR_ValidFunctionNameSniff;
|
||||
use PHP_CodeSniffer_File as File;
|
||||
|
||||
/**
|
||||
* Enforces WordPress function name and method name format, based upon Squiz code.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#naming-conventions
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*
|
||||
* Last synced with parent class July 2016 up to commit 4fea2e651109e41066a81e22e004d851fb1287f6.
|
||||
* @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php
|
||||
*
|
||||
* {@internal While this class extends the PEAR parent, it does not actually use the checks
|
||||
* contained in the parent. It only uses the properties and the token registration from the parent.}}
|
||||
*/
|
||||
class ValidFunctionNameSniff extends PHPCS_PEAR_ValidFunctionNameSniff {
|
||||
|
||||
/**
|
||||
* Additional double underscore prefixed methods specific to certain PHP native extensions.
|
||||
*
|
||||
* Currently only handles the SoapClient Extension.
|
||||
*
|
||||
* @link http://php.net/manual/en/class.soapclient.php
|
||||
*
|
||||
* @var array <string method name> => <string class name>
|
||||
*/
|
||||
private $methodsDoubleUnderscore = array(
|
||||
'doRequest' => 'SoapClient',
|
||||
'getFunctions' => 'SoapClient',
|
||||
'getLastRequest' => 'SoapClient',
|
||||
'getLastRequestHeaders' => 'SoapClient',
|
||||
'getLastResponse' => 'SoapClient',
|
||||
'getLastResponseHeaders' => 'SoapClient',
|
||||
'getTypes' => 'SoapClient',
|
||||
'setCookie' => 'SoapClient',
|
||||
'setLocation' => 'SoapClient',
|
||||
'setSoapHeaders' => 'SoapClient',
|
||||
'soapCall' => 'SoapClient',
|
||||
);
|
||||
|
||||
/**
|
||||
* Processes the tokens outside the scope.
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
|
||||
* @param int $stackPtr The position where this token was
|
||||
* found.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processTokenOutsideScope( File $phpcsFile, $stackPtr ) {
|
||||
$functionName = $phpcsFile->getDeclarationName( $stackPtr );
|
||||
|
||||
if ( ! isset( $functionName ) ) {
|
||||
// Ignore closures.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( '' === ltrim( $functionName, '_' ) ) {
|
||||
// Ignore special functions.
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this a magic function ? I.e., it is prefixed with "__" ?
|
||||
// Outside class scope this basically just means __autoload().
|
||||
if ( 0 === strpos( $functionName, '__' ) ) {
|
||||
$magicPart = strtolower( substr( $functionName, 2 ) );
|
||||
if ( isset( $this->magicFunctions[ $magicPart ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = 'Function name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore';
|
||||
$errorData = array( $functionName );
|
||||
$phpcsFile->addError( $error, $stackPtr, 'FunctionDoubleUnderscore', $errorData );
|
||||
}
|
||||
|
||||
if ( strtolower( $functionName ) !== $functionName ) {
|
||||
$error = 'Function name "%s" is not in snake case format, try "%s"';
|
||||
$errorData = array(
|
||||
$functionName,
|
||||
$this->get_name_suggestion( $functionName ),
|
||||
);
|
||||
$phpcsFile->addError( $error, $stackPtr, 'FunctionNameInvalid', $errorData );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the tokens within the scope.
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
|
||||
* @param int $stackPtr The position where this token was
|
||||
* found.
|
||||
* @param int $currScope The position of the current scope.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processTokenWithinScope( File $phpcsFile, $stackPtr, $currScope ) {
|
||||
$methodName = $phpcsFile->getDeclarationName( $stackPtr );
|
||||
|
||||
if ( ! isset( $methodName ) ) {
|
||||
// Ignore closures.
|
||||
return;
|
||||
}
|
||||
|
||||
$className = $phpcsFile->getDeclarationName( $currScope );
|
||||
|
||||
// Ignore special functions.
|
||||
if ( '' === ltrim( $methodName, '_' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PHP4 constructors are allowed to break our rules.
|
||||
if ( $methodName === $className ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PHP4 destructors are allowed to break our rules.
|
||||
if ( '_' . $className === $methodName ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extended = $phpcsFile->findExtendedClassName( $currScope );
|
||||
$interfaces = $phpcsFile->findImplementedInterfaceNames( $currScope );
|
||||
|
||||
// If this is a child class or interface implementation, it may have to use camelCase or double underscores.
|
||||
if ( ! empty( $extended ) || ! empty( $interfaces ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this a magic method ? I.e. is it prefixed with "__" ?
|
||||
if ( 0 === strpos( $methodName, '__' ) ) {
|
||||
$magicPart = strtolower( substr( $methodName, 2 ) );
|
||||
if ( isset( $this->magicMethods[ $magicPart ] ) || isset( $this->methodsDoubleUnderscore[ $magicPart ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore';
|
||||
$errorData = array( $className . '::' . $methodName );
|
||||
$phpcsFile->addError( $error, $stackPtr, 'MethodDoubleUnderscore', $errorData );
|
||||
}
|
||||
|
||||
// Check for all lowercase.
|
||||
if ( strtolower( $methodName ) !== $methodName ) {
|
||||
$error = 'Method name "%s" in class %s is not in snake case format, try "%s"';
|
||||
$errorData = array(
|
||||
$methodName,
|
||||
$className,
|
||||
$this->get_name_suggestion( $methodName ),
|
||||
);
|
||||
$phpcsFile->addError( $error, $stackPtr, 'MethodNameInvalid', $errorData );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the existing function/method name to one which complies with the naming conventions.
|
||||
*
|
||||
* @param string $name The function/method name.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_name_suggestion( $name ) {
|
||||
$suggested = preg_replace( '/([A-Z])/', '_$1', $name );
|
||||
$suggested = strtolower( $suggested );
|
||||
$suggested = str_replace( '__', '_', $suggested );
|
||||
$suggested = trim( $suggested, '_' );
|
||||
return $suggested;
|
||||
}
|
||||
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Coding Standard.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace WordPress\Sniffs\NamingConventions;
|
||||
|
||||
use WordPress\AbstractFunctionParameterSniff;
|
||||
|
||||
/**
|
||||
* Use lowercase letters in action and filter names. Separate words via underscores.
|
||||
*
|
||||
* This sniff is only testing the hook invoke functions as when using 'add_action'/'add_filter'
|
||||
* you can't influence the hook name.
|
||||
*
|
||||
* Hook names invoked with `do_action_deprecated()` and `apply_filters_deprecated()` are ignored.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#naming-conventions
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.10.0
|
||||
* @since 0.11.0 Extends the WordPress_AbstractFunctionParameterSniff class.
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*/
|
||||
class ValidHookNameSniff extends AbstractFunctionParameterSniff {
|
||||
|
||||
/**
|
||||
* Additional word separators.
|
||||
*
|
||||
* This public variable allows providing additional word separators which
|
||||
* will be allowed in hook names via a property in the phpcs.xml config file.
|
||||
*
|
||||
* Example usage:
|
||||
* <rule ref="WordPress.NamingConventions.ValidHookName">
|
||||
* <properties>
|
||||
* <property name="additionalWordDelimiters" value="-"/>
|
||||
* </properties>
|
||||
* </rule>
|
||||
*
|
||||
* Provide several extra delimiters as one string:
|
||||
* <rule ref="WordPress.NamingConventions.ValidHookName">
|
||||
* <properties>
|
||||
* <property name="additionalWordDelimiters" value="-/."/>
|
||||
* </properties>
|
||||
* </rule>
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $additionalWordDelimiters = '';
|
||||
|
||||
/**
|
||||
* Regular expression to test for correct punctuation of a hook name.
|
||||
*
|
||||
* The placeholder will be replaced by potentially provided additional
|
||||
* word delimiters in the `prepare_regex()` method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $punctuation_regex = '`[^\w%s]`';
|
||||
|
||||
/**
|
||||
* Groups of function to restrict.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups() {
|
||||
$this->target_functions = $this->hookInvokeFunctions;
|
||||
return parent::getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the parameters of a matched function.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
* @param 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 ) {
|
||||
// Ignore deprecated hook names.
|
||||
if ( strpos( $matched_content, '_deprecated' ) > 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $parameters[1] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$regex = $this->prepare_regex();
|
||||
|
||||
$case_errors = 0;
|
||||
$underscores = 0;
|
||||
$content = array();
|
||||
$expected = array();
|
||||
|
||||
for ( $i = $parameters[1]['start']; $i <= $parameters[1]['end']; $i++ ) {
|
||||
$content[ $i ] = $this->tokens[ $i ]['content'];
|
||||
$expected[ $i ] = $this->tokens[ $i ]['content'];
|
||||
|
||||
if ( \in_array( $this->tokens[ $i ]['code'], array( \T_CONSTANT_ENCAPSED_STRING, \T_DOUBLE_QUOTED_STRING ), true ) ) {
|
||||
$string = $this->strip_quotes( $this->tokens[ $i ]['content'] );
|
||||
|
||||
/*
|
||||
* Here be dragons - a double quoted string can contain extrapolated variables
|
||||
* which don't have to comply with these rules.
|
||||
*/
|
||||
if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] ) {
|
||||
$transform = $this->transform_complex_string( $string, $regex );
|
||||
$case_transform = $this->transform_complex_string( $string, $regex, 'case' );
|
||||
$punct_transform = $this->transform_complex_string( $string, $regex, 'punctuation' );
|
||||
} else {
|
||||
$transform = $this->transform( $string, $regex );
|
||||
$case_transform = $this->transform( $string, $regex, 'case' );
|
||||
$punct_transform = $this->transform( $string, $regex, 'punctuation' );
|
||||
}
|
||||
|
||||
if ( $string === $transform ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] ) {
|
||||
$expected[ $i ] = '"' . $transform . '"';
|
||||
} else {
|
||||
$expected[ $i ] = '\'' . $transform . '\'';
|
||||
}
|
||||
|
||||
if ( $string !== $case_transform ) {
|
||||
$case_errors++;
|
||||
}
|
||||
if ( $string !== $punct_transform ) {
|
||||
$underscores++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
implode( '', $expected ),
|
||||
implode( '', $content ),
|
||||
);
|
||||
|
||||
if ( $case_errors > 0 ) {
|
||||
$error = 'Hook names should be lowercase. Expected: %s, but found: %s.';
|
||||
$this->phpcsFile->addError( $error, $stackPtr, 'NotLowercase', $data );
|
||||
}
|
||||
if ( $underscores > 0 ) {
|
||||
$error = 'Words in hook names should be separated using underscores. Expected: %s, but found: %s.';
|
||||
$this->phpcsFile->addWarning( $error, $stackPtr, 'UseUnderscores', $data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the punctuation regular expression.
|
||||
*
|
||||
* Merges the existing regular expression with potentially provided extra word delimiters to allow.
|
||||
* This is done 'late' and for each found token as otherwise inline `@codingStandardsChangeSetting`
|
||||
* directives would be ignored.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function prepare_regex() {
|
||||
$extra = '';
|
||||
if ( '' !== $this->additionalWordDelimiters && \is_string( $this->additionalWordDelimiters ) ) {
|
||||
$extra = preg_quote( $this->additionalWordDelimiters, '`' );
|
||||
}
|
||||
|
||||
return sprintf( $this->punctuation_regex, $extra );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an arbitrary string to lowercase and replace punctuation and spaces with underscores.
|
||||
*
|
||||
* @param string $string The target string.
|
||||
* @param string $regex The punctuation regular expression to use.
|
||||
* @param string $transform_type Whether to a partial or complete transform.
|
||||
* Valid values are: 'full', 'case', 'punctuation'.
|
||||
* @return string
|
||||
*/
|
||||
protected function transform( $string, $regex, $transform_type = 'full' ) {
|
||||
|
||||
switch ( $transform_type ) {
|
||||
case 'case':
|
||||
return strtolower( $string );
|
||||
|
||||
case 'punctuation':
|
||||
return preg_replace( $regex, '_', $string );
|
||||
|
||||
case 'full':
|
||||
default:
|
||||
return preg_replace( $regex, '_', strtolower( $string ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a complex string which may contain variable extrapolation.
|
||||
*
|
||||
* @param string $string The target string.
|
||||
* @param string $regex The punctuation regular expression to use.
|
||||
* @param string $transform_type Whether to a partial or complete transform.
|
||||
* Valid values are: 'full', 'case', 'punctuation'.
|
||||
* @return string
|
||||
*/
|
||||
protected function transform_complex_string( $string, $regex, $transform_type = 'full' ) {
|
||||
$output = preg_split( '`([\{\}\$\[\] ])`', $string, -1, \PREG_SPLIT_DELIM_CAPTURE );
|
||||
|
||||
$is_variable = false;
|
||||
$has_braces = false;
|
||||
$braces = 0;
|
||||
|
||||
foreach ( $output as $i => $part ) {
|
||||
if ( \in_array( $part, array( '$', '{' ), true ) ) {
|
||||
$is_variable = true;
|
||||
if ( '{' === $part ) {
|
||||
$has_braces = true;
|
||||
$braces++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( true === $is_variable ) {
|
||||
if ( '[' === $part ) {
|
||||
$has_braces = true;
|
||||
$braces++;
|
||||
}
|
||||
if ( \in_array( $part, array( '}', ']' ), true ) ) {
|
||||
$braces--;
|
||||
}
|
||||
if ( false === $has_braces && ' ' === $part ) {
|
||||
$is_variable = false;
|
||||
$output[ $i ] = $this->transform( $part, $regex, $transform_type );
|
||||
}
|
||||
|
||||
if ( ( true === $has_braces && 0 === $braces ) && false === \in_array( $output[ ( $i + 1 ) ], array( '{', '[' ), true ) ) {
|
||||
$has_braces = false;
|
||||
$is_variable = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$output[ $i ] = $this->transform( $part, $regex, $transform_type );
|
||||
}
|
||||
|
||||
return implode( '', $output );
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+336
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Coding Standard.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace WordPress\Sniffs\NamingConventions;
|
||||
|
||||
use PHP_CodeSniffer_Standards_AbstractVariableSniff as PHPCS_AbstractVariableSniff;
|
||||
use PHP_CodeSniffer_File as File;
|
||||
use PHP_CodeSniffer_Tokens as Tokens;
|
||||
use WordPress\Sniff;
|
||||
|
||||
/**
|
||||
* Checks the naming of variables and member variables.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#naming-conventions
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*
|
||||
* Last synced with base class June 2018 at commit 78ddbae97cac078f09928bf89e3ab9e53ad2ace0.
|
||||
* @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php
|
||||
* One change from upstream deferred till later (PHPCS 3.3.0+):
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/1048#issuecomment-364282100
|
||||
*/
|
||||
class ValidVariableNameSniff extends PHPCS_AbstractVariableSniff {
|
||||
|
||||
/**
|
||||
* PHP Reserved Vars.
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @since 0.11.0 Changed visibility from public to protected.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $php_reserved_vars = array(
|
||||
'_SERVER' => true,
|
||||
'_GET' => true,
|
||||
'_POST' => true,
|
||||
'_REQUEST' => true,
|
||||
'_SESSION' => true,
|
||||
'_ENV' => true,
|
||||
'_COOKIE' => true,
|
||||
'_FILES' => true,
|
||||
'GLOBALS' => true,
|
||||
'http_response_header' => true,
|
||||
'HTTP_RAW_POST_DATA' => true,
|
||||
'php_errormsg' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Mixed-case variables used by WordPress.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $wordpress_mixed_case_vars = array(
|
||||
'EZSQL_ERROR' => true,
|
||||
'GETID3_ERRORARRAY' => true,
|
||||
'is_IE' => true,
|
||||
'is_IIS' => true,
|
||||
'is_macIE' => true,
|
||||
'is_NS4' => true,
|
||||
'is_winIE' => true,
|
||||
'PHP_SELF' => true,
|
||||
'post_ID' => true,
|
||||
'user_ID' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of member variables that can have mixed case.
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @since 0.11.0 Changed from public to protected.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $whitelisted_mixed_case_member_var_names = array(
|
||||
'ID' => true,
|
||||
'comment_ID' => true,
|
||||
'comment_post_ID' => true,
|
||||
'post_ID' => true,
|
||||
'comment_author_IP' => true,
|
||||
'cat_ID' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Custom list of properties which can have mixed case.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*
|
||||
* @var string|string[]
|
||||
*/
|
||||
public $customPropertiesWhitelist = array();
|
||||
|
||||
/**
|
||||
* Cache of previously added custom functions.
|
||||
*
|
||||
* Prevents having to do the same merges over and over again.
|
||||
*
|
||||
* @since 0.10.0
|
||||
* @since 0.11.0 - Name changed from $addedCustomVariables.
|
||||
* - Changed the format from simple bool to array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $addedCustomProperties = array(
|
||||
'properties' => null,
|
||||
'variables' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
* Custom list of properties which can have mixed case.
|
||||
*
|
||||
* @since 0.10.0
|
||||
* @deprecated 0.11.0 Use $customPropertiesWhitelist instead.
|
||||
*
|
||||
* @var string|string[]
|
||||
*/
|
||||
public $customVariablesWhitelist = array();
|
||||
|
||||
/**
|
||||
* Processes this test, when one of its tokens is encountered.
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned.
|
||||
* @param int $stack_ptr The position of the current token in the
|
||||
* stack passed in $tokens.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processVariable( File $phpcs_file, $stack_ptr ) {
|
||||
|
||||
$tokens = $phpcs_file->getTokens();
|
||||
$var_name = ltrim( $tokens[ $stack_ptr ]['content'], '$' );
|
||||
|
||||
// If it's a php reserved var, then its ok.
|
||||
if ( isset( $this->php_reserved_vars[ $var_name ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge any custom variables with the defaults.
|
||||
$this->mergeWhiteList( $phpcs_file );
|
||||
|
||||
// Likewise if it is a mixed-case var used by WordPress core.
|
||||
if ( isset( $this->wordpress_mixed_case_vars[ $var_name ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$obj_operator = $phpcs_file->findNext( Tokens::$emptyTokens, ( $stack_ptr + 1 ), null, true );
|
||||
if ( \T_OBJECT_OPERATOR === $tokens[ $obj_operator ]['code'] ) {
|
||||
// Check to see if we are using a variable from an object.
|
||||
$var = $phpcs_file->findNext( Tokens::$emptyTokens, ( $obj_operator + 1 ), null, true );
|
||||
if ( \T_STRING === $tokens[ $var ]['code'] ) {
|
||||
$bracket = $phpcs_file->findNext( Tokens::$emptyTokens, ( $var + 1 ), null, true );
|
||||
if ( \T_OPEN_PARENTHESIS !== $tokens[ $bracket ]['code'] ) {
|
||||
$obj_var_name = $tokens[ $var ]['content'];
|
||||
|
||||
// There is no way for us to know if the var is public or
|
||||
// private, so we have to ignore a leading underscore if there is
|
||||
// one and just check the main part of the variable name.
|
||||
$original_var_name = $obj_var_name;
|
||||
if ( '_' === substr( $obj_var_name, 0, 1 ) ) {
|
||||
$obj_var_name = substr( $obj_var_name, 1 );
|
||||
}
|
||||
|
||||
if ( ! isset( $this->whitelisted_mixed_case_member_var_names[ $obj_var_name ] ) && self::isSnakeCase( $obj_var_name ) === false ) {
|
||||
$error = 'Object property "%s" is not in valid snake_case format';
|
||||
$data = array( $original_var_name );
|
||||
$phpcs_file->addError( $error, $var, 'NotSnakeCaseMemberVar', $data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$in_class = false;
|
||||
$obj_operator = $phpcs_file->findPrevious( Tokens::$emptyTokens, ( $stack_ptr - 1 ), null, true );
|
||||
if ( \T_DOUBLE_COLON === $tokens[ $obj_operator ]['code'] || \T_OBJECT_OPERATOR === $tokens[ $obj_operator ]['code'] ) {
|
||||
// The variable lives within a class, and is referenced like
|
||||
// this: MyClass::$_variable or $class->variable.
|
||||
$in_class = true;
|
||||
}
|
||||
|
||||
// There is no way for us to know if the var is public or private,
|
||||
// so we have to ignore a leading underscore if there is one and just
|
||||
// check the main part of the variable name.
|
||||
$original_var_name = $var_name;
|
||||
if ( '_' === substr( $var_name, 0, 1 ) && true === $in_class ) {
|
||||
$var_name = substr( $var_name, 1 );
|
||||
}
|
||||
|
||||
if ( self::isSnakeCase( $var_name ) === false ) {
|
||||
if ( $in_class && ! isset( $this->whitelisted_mixed_case_member_var_names[ $var_name ] ) ) {
|
||||
$error = 'Object property "%s" is not in valid snake_case format';
|
||||
$error_name = 'NotSnakeCaseMemberVar';
|
||||
} elseif ( ! $in_class ) {
|
||||
$error = 'Variable "%s" is not in valid snake_case format';
|
||||
$error_name = 'NotSnakeCase';
|
||||
}
|
||||
|
||||
if ( isset( $error, $error_name ) ) {
|
||||
$data = array( $original_var_name );
|
||||
$phpcs_file->addError( $error, $stack_ptr, $error_name, $data );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes class member variables.
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned.
|
||||
* @param int $stack_ptr The position of the current token in the
|
||||
* stack passed in $tokens.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processMemberVar( File $phpcs_file, $stack_ptr ) {
|
||||
|
||||
$tokens = $phpcs_file->getTokens();
|
||||
|
||||
$var_name = ltrim( $tokens[ $stack_ptr ]['content'], '$' );
|
||||
$member_props = $phpcs_file->getMemberProperties( $stack_ptr );
|
||||
if ( empty( $member_props ) ) {
|
||||
// Couldn't get any info about this variable, which
|
||||
// generally means it is invalid or possibly has a parse
|
||||
// error. Any errors will be reported by the core, so
|
||||
// we can ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge any custom variables with the defaults.
|
||||
$this->mergeWhiteList( $phpcs_file );
|
||||
|
||||
$error_data = array( $var_name );
|
||||
if ( ! isset( $this->whitelisted_mixed_case_member_var_names[ $var_name ] ) && false === self::isSnakeCase( $var_name ) ) {
|
||||
$error = 'Member variable "%s" is not in valid snake_case format.';
|
||||
$phpcs_file->addError( $error, $stack_ptr, 'MemberNotSnakeCase', $error_data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the variable found within a double quoted string.
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned.
|
||||
* @param int $stack_ptr The position of the double quoted
|
||||
* string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processVariableInString( File $phpcs_file, $stack_ptr ) {
|
||||
|
||||
$tokens = $phpcs_file->getTokens();
|
||||
|
||||
if ( preg_match_all( '|[^\\\]\${?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|', $tokens[ $stack_ptr ]['content'], $matches ) > 0 ) {
|
||||
|
||||
// Merge any custom variables with the defaults.
|
||||
$this->mergeWhiteList( $phpcs_file );
|
||||
|
||||
foreach ( $matches[1] as $var_name ) {
|
||||
// If it's a php reserved var, then its ok.
|
||||
if ( isset( $this->php_reserved_vars[ $var_name ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Likewise if it is a mixed-case var used by WordPress core.
|
||||
if ( isset( $this->wordpress_mixed_case_vars[ $var_name ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === self::isSnakeCase( $var_name ) ) {
|
||||
$error = 'Variable "%s" is not in valid snake_case format';
|
||||
$data = array( $var_name );
|
||||
$phpcs_file->addError( $error, $stack_ptr, 'StringNotSnakeCase', $data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the variable is in snake_case.
|
||||
*
|
||||
* @param string $var_name Variable name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSnakeCase( $var_name ) {
|
||||
return (bool) preg_match( '/^[a-z0-9_]+$/', $var_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a custom whitelist provided via a custom ruleset with the predefined whitelist,
|
||||
* if we haven't already.
|
||||
*
|
||||
* @since 0.10.0
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeWhiteList( File $phpcs_file ) {
|
||||
if ( $this->customPropertiesWhitelist !== $this->addedCustomProperties['properties']
|
||||
|| $this->customVariablesWhitelist !== $this->addedCustomProperties['variables']
|
||||
) {
|
||||
// Fix property potentially passed as comma-delimited string.
|
||||
$customProperties = Sniff::merge_custom_array( $this->customPropertiesWhitelist, array(), false );
|
||||
|
||||
if ( ! empty( $this->customVariablesWhitelist ) ) {
|
||||
$customProperties = Sniff::merge_custom_array(
|
||||
$this->customVariablesWhitelist,
|
||||
$customProperties,
|
||||
false
|
||||
);
|
||||
|
||||
$phpcs_file->addWarning(
|
||||
'The customVariablesWhitelist property is deprecated in favor of customPropertiesWhitelist.',
|
||||
0,
|
||||
'DeprecatedCustomVariablesWhitelist'
|
||||
);
|
||||
}
|
||||
|
||||
$this->whitelisted_mixed_case_member_var_names = Sniff::merge_custom_array(
|
||||
$customProperties,
|
||||
$this->whitelisted_mixed_case_member_var_names
|
||||
);
|
||||
|
||||
$this->addedCustomProperties['properties'] = $this->customPropertiesWhitelist;
|
||||
$this->addedCustomProperties['variables'] = $this->customVariablesWhitelist;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user