<?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 WordPressCS\WordPress\Sniffs\WP;

use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;

/**
 * Discourages the use of various functions and suggests (WordPress) alternatives.
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.11.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 * @since   1.0.0  - Takes the minimum supported WP version into account.
 *                 - Takes exceptions based on passed parameters into account.
 *
 * @uses    \WordPressCS\WordPress\Sniff::$minimum_supported_version
 */
class AlternativeFunctionsSniff extends AbstractFunctionRestrictionsSniff {

	/**
	 * Local input streams which should not be flagged for the file system function checks.
	 *
	 * @link http://php.net/manual/en/wrappers.php.php
	 *
	 * @var array
	 */
	protected $allowed_local_streams = array(
		'php://input'  => true,
		'php://output' => true,
		'php://stdin'  => true,
		'php://stdout' => true,
		'php://stderr' => true,
	);

	/**
	 * Local input streams which should not be flagged for the file system function checks if
	 * the $filename starts with them.
	 *
	 * @link http://php.net/manual/en/wrappers.php.php
	 *
	 * @var array
	 */
	protected $allowed_local_stream_partials = array(
		'php://temp/',
		'php://fd/',
	);

	/**
	 * Local input stream constants which should not be flagged for the file system function checks.
	 *
	 * @link http://php.net/manual/en/wrappers.php.php
	 *
	 * @var array
	 */
	protected $allowed_local_stream_constants = array(
		'STDIN'  => true,
		'STDOUT' => true,
		'STDERR' => true,
	);

	/**
	 * Groups of functions to restrict.
	 *
	 * Example: groups => array(
	 *  'lambda' => array(
	 *      'type'      => 'error' | 'warning',
	 *      'message'   => 'Use anonymous functions instead please!',
	 *      'since'     => '4.9.0', //=> the WP version in which the alternative became available.
	 *      'functions' => array( 'file_get_contents', 'create_function' ),
	 *  )
	 * )
	 *
	 * @return array
	 */
	public function getGroups() {
		return array(
			'curl' => array(
				'type'      => 'warning',
				'message'   => 'Using cURL functions is highly discouraged. Use wp_remote_get() instead.',
				'since'     => '2.7.0',
				'functions' => array(
					'curl_*',
				),
			),

			'parse_url' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.',
				'since'     => '4.4.0',
				'functions' => array(
					'parse_url',
				),
			),

			'json_encode' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged. Use wp_json_encode() instead.',
				'since'     => '4.1.0',
				'functions' => array(
					'json_encode',
				),
			),

			'file_get_contents' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged. Use wp_remote_get() for remote URLs instead.',
				'since'     => '2.7.0',
				'functions' => array(
					'file_get_contents',
				),
			),

			'file_system_read' => array(
				'type'      => 'warning',
				'message'   => 'File operations should use WP_Filesystem methods instead of direct PHP filesystem calls. Found: %s()',
				'since'     => '2.5.0',
				'functions' => array(
					'readfile',
					'fclose',
					'fopen',
					'fread',
					'fwrite',
					'file_put_contents',
					'fsockopen',
					'pfsockopen',
				),
			),

			'strip_tags' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged. Use the more comprehensive wp_strip_all_tags() instead.',
				'since'     => '2.9.0',
				'functions' => array(
					'strip_tags',
				),
			),

			'rand_seeding' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged. Rand seeding is not necessary when using the wp_rand() function (as you should).',
				'since'     => '2.6.2',
				'functions' => array(
					'srand',
					'mt_srand',
				),
			),

			'rand' => array(
				'type'      => 'warning',
				'message'   => '%s() is discouraged. Use the far less predictable wp_rand() instead.',
				'since'     => '2.6.2',
				'functions' => array(
					'rand',
					'mt_rand',
				),
			),
		);
	}

	/**
	 * Process a matched token.
	 *
	 * @param int    $stackPtr        The position of the current token in the stack.
	 * @param string $group_name      The name of the group which was matched.
	 * @param string $matched_content The token content (function name) which was matched.
	 *
	 * @return int|void Integer stack pointer to skip forward or void to continue
	 *                  normal file processing.
	 */
	public function process_matched_token( $stackPtr, $group_name, $matched_content ) {

		$this->get_wp_version_from_cl();

		/*
		 * Deal with exceptions.
		 */
		switch ( $matched_content ) {
			case 'strip_tags':
				/*
				 * The function `wp_strip_all_tags()` is only a valid alternative when
				 * only the first parameter is passed to `strip_tags()`.
				 */
				if ( $this->get_function_call_parameter_count( $stackPtr ) !== 1 ) {
					return;
				}

				break;

			case 'wp_parse_url':
				/*
				 * Before WP 4.7.0, the function `wp_parse_url()` was only a valid alternative
				 * if no second param was passed to `parse_url()`.
				 *
				 * @see https://developer.wordpress.org/reference/functions/wp_parse_url/#changelog
				 */
				if ( $this->get_function_call_parameter_count( $stackPtr ) !== 1
					&& version_compare( $this->minimum_supported_version, '4.7.0', '<' )
				) {
					return;
				}

				break;

			case 'file_get_contents':
				/*
				 * Using `wp_remote_get()` will only work for remote URLs.
				 * See if we can determine is this function call is for a local file and if so, bow out.
				 */
				$params = $this->get_function_call_parameters( $stackPtr );

				if ( isset( $params[2] ) && 'true' === $params[2]['raw'] ) {
					// Setting `$use_include_path` to `true` is only relevant for local files.
					return;
				}

				if ( isset( $params[1] ) === false ) {
					// If the file to get is not set, this is a non-issue anyway.
					return;
				}

				if ( strpos( $params[1]['raw'], 'http:' ) !== false
					|| strpos( $params[1]['raw'], 'https:' ) !== false
				) {
					// Definitely a URL, throw notice.
					break;
				}

				if ( preg_match( '`\b(?:ABSPATH|WP_(?:CONTENT|PLUGIN)_DIR|WPMU_PLUGIN_DIR|TEMPLATEPATH|STYLESHEETPATH|(?:MU)?PLUGINDIR)\b`', $params[1]['raw'] ) === 1 ) {
					// Using any of the constants matched in this regex is an indicator of a local file.
					return;
				}

				if ( preg_match( '`(?:get_home_path|plugin_dir_path|get_(?:stylesheet|template)_directory|wp_upload_dir)\s*\(`i', $params[1]['raw'] ) === 1 ) {
					// Using any of the functions matched in the regex is an indicator of a local file.
					return;
				}

				if ( $this->is_local_data_stream( $params[1]['raw'] ) === true ) {
					// Local data stream.
					return;
				}

				unset( $params );

				break;

			case 'readfile':
			case 'fopen':
			case 'file_put_contents':
				/*
				 * Allow for handling raw data streams from the request body.
				 */
				$first_param = $this->get_function_call_parameter( $stackPtr, 1 );

				if ( false === $first_param ) {
					// If the file to work with is not set, local data streams don't come into play.
					break;
				}

				if ( $this->is_local_data_stream( $first_param['raw'] ) === true ) {
					// Local data stream.
					return;
				}

				unset( $first_param );

				break;

			case 'curl_version':
				// Curl version doesn't actually create a connection.
				return;
		}

		if ( ! isset( $this->groups[ $group_name ]['since'] ) ) {
			return parent::process_matched_token( $stackPtr, $group_name, $matched_content );
		}

		// Verify if the alternative is available in the minimum supported WP version.
		if ( version_compare( $this->groups[ $group_name ]['since'], $this->minimum_supported_version, '<=' ) ) {
			return parent::process_matched_token( $stackPtr, $group_name, $matched_content );
		}
	}

	/**
	 * Determine based on the "raw" parameter value, whether a file parameter points to
	 * a local data stream.
	 *
	 * @param string $raw_param_value Raw parameter value.
	 *
	 * @return bool True if this is a local data stream. False otherwise.
	 */
	protected function is_local_data_stream( $raw_param_value ) {

		$raw_stripped = $this->strip_quotes( $raw_param_value );
		if ( isset( $this->allowed_local_streams[ $raw_stripped ] )
			|| isset( $this->allowed_local_stream_constants[ $raw_param_value ] )
		) {
			return true;
		}

		foreach ( $this->allowed_local_stream_partials as $partial ) {
			if ( strpos( $raw_stripped, $partial ) === 0 ) {
				return true;
			}
		}

		return false;
	}
}


<?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 WordPressCS\WordPress\Sniffs\WhiteSpace;

use WordPressCS\WordPress\Sniff;
use WordPressCS\WordPress\PHPCSHelper;

/**
 * Enforces using spaces for mid-line alignment.
 *
 * @link    https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.12.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 */
class DisallowInlineTabsSniff extends Sniff {

	/**
	 * The --tab-width CLI value that is being used.
	 *
	 * @var int
	 */
	private $tab_width;

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {
		return array(
			\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 Integer stack pointer to skip the rest of the file.
	 */
	public function process_token( $stackPtr ) {
		if ( ! isset( $this->tab_width ) ) {
			$this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile );
		}

		$check_tokens = array(
			\T_WHITESPACE             => true,
			\T_DOC_COMMENT_WHITESPACE => true,
			\T_DOC_COMMENT_STRING     => true,
		);

		for ( $i = ( $stackPtr + 1 ); $i < $this->phpcsFile->numTokens; $i++ ) {
			// Skip all non-whitespace tokens and skip whitespace at the start of a new line.
			if ( ! isset( $check_tokens[ $this->tokens[ $i ]['code'] ] ) || 1 === $this->tokens[ $i ]['column'] ) {
				continue;
			}

			// If tabs are being converted to spaces by the tokenizer, the
			// original content should be checked instead of the converted content.
			if ( isset( $this->tokens[ $i ]['orig_content'] ) ) {
				$content = $this->tokens[ $i ]['orig_content'];
			} else {
				$content = $this->tokens[ $i ]['content'];
			}

			if ( '' === $content || strpos( $content, "\t" ) === false ) {
				continue;
			}

			$fix = $this->phpcsFile->addFixableError(
				'Spaces must be used for mid-line alignment; tabs are not allowed',
				$i,
				'NonIndentTabsUsed'
			);
			if ( true === $fix ) {
				if ( isset( $this->tokens[ $i ]['orig_content'] ) ) {
					// Use the replacement that PHPCS has already done.
					$this->phpcsFile->fixer->replaceToken( $i, $this->tokens[ $i ]['content'] );
				} else {
					// Replace tabs with spaces, using an indent of $tab_width.
					// Other sniffs can then correct the indent if they need to.
					$spaces     = str_repeat( ' ', $this->tab_width );
					$newContent = str_replace( "\t", $spaces, $this->tokens[ $i ]['content'] );
					$this->phpcsFile->fixer->replaceToken( $i, $newContent );
				}
			}
		}

		// Ignore the rest of the file.
		return ( $this->phpcsFile->numTokens + 1 );
	}

}


<?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 WordPressCS\WordPress\Sniffs\DB;

use WordPressCS\WordPress\Sniff;
use PHP_CodeSniffer\Util\Tokens;

/**
 * Flag Database direct queries.
 *
 * @link    https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#direct-database-queries
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.3.0
 * @since   0.6.0  Removed the add_unique_message() function as it is no longer needed.
 * @since   0.11.0 This class now extends the WordPressCS native `Sniff` class.
 * @since   0.13.0 Class name changed: this class is now namespaced.
 * @since   1.0.0  This sniff has been moved from the `VIP` category to the `DB` category.
 */
class DirectDatabaseQuerySniff extends Sniff {

	/**
	 * List of custom cache get functions.
	 *
	 * @since 0.6.0
	 *
	 * @var string|string[]
	 */
	public $customCacheGetFunctions = array();

	/**
	 * List of custom cache set functions.
	 *
	 * @since 0.6.0
	 *
	 * @var string|string[]
	 */
	public $customCacheSetFunctions = array();

	/**
	 * List of custom cache delete functions.
	 *
	 * @since 0.6.0
	 *
	 * @var string|string[]
	 */
	public $customCacheDeleteFunctions = array();

	/**
	 * Cache of previously added custom functions.
	 *
	 * Prevents having to do the same merges over and over again.
	 *
	 * @since 0.11.0
	 *
	 * @var array
	 */
	protected $addedCustomFunctions = array(
		'cacheget'    => array(),
		'cacheset'    => array(),
		'cachedelete' => array(),
	);

	/**
	 * The lists of $wpdb methods.
	 *
	 * @since 0.6.0
	 * @since 0.11.0 Changed from static to non-static.
	 *
	 * @var array[]
	 */
	protected $methods = array(
		'cachable' => array(
			'delete'      => true,
			'get_var'     => true,
			'get_col'     => true,
			'get_row'     => true,
			'get_results' => true,
			'query'       => true,
			'replace'     => true,
			'update'      => true,
		),
		'noncachable' => array(
			'insert' => true,
		),
	);

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {
		return array(
			\T_VARIABLE,
		);
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @param int $stackPtr The position of the current token in the stack.
	 *
	 * @return void
	 */
	public function process_token( $stackPtr ) {

		// Check for $wpdb variable.
		if ( '$wpdb' !== $this->tokens[ $stackPtr ]['content'] ) {
			return;
		}

		$is_object_call = $this->phpcsFile->findNext( \T_OBJECT_OPERATOR, ( $stackPtr + 1 ), null, false, null, true );
		if ( false === $is_object_call ) {
			return; // This is not a call to the wpdb object.
		}

		$methodPtr = $this->phpcsFile->findNext( array( \T_WHITESPACE ), ( $is_object_call + 1 ), null, true, null, true );
		$method    = $this->tokens[ $methodPtr ]['content'];

		$this->mergeFunctionLists();

		if ( ! isset( $this->methods['all'][ $method ] ) ) {
			return;
		}

		$endOfStatement   = $this->phpcsFile->findNext( \T_SEMICOLON, ( $stackPtr + 1 ), null, false, null, true );
		$endOfLineComment = '';
		for ( $i = ( $endOfStatement + 1 ); $i < $this->phpcsFile->numTokens; $i++ ) {

			if ( $this->tokens[ $i ]['line'] !== $this->tokens[ $endOfStatement ]['line'] ) {
				break;
			}

			if ( \T_COMMENT === $this->tokens[ $i ]['code'] ) {
				$endOfLineComment .= $this->tokens[ $i ]['content'];
			}
		}

		$whitelisted_db_call = false;
		if ( preg_match( '/db call\W*(?:ok|pass|clear|whitelist)/i', $endOfLineComment ) ) {
			$whitelisted_db_call = true;
		}

		// Check for Database Schema Changes.
		for ( $_pos = ( $stackPtr + 1 ); $_pos < $endOfStatement; $_pos++ ) {
			$_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $_pos, $endOfStatement, false, null, true );
			if ( false === $_pos ) {
				break;
			}

			if ( preg_match( '#\b(?:ALTER|CREATE|DROP)\b#i', $this->tokens[ $_pos ]['content'] ) > 0 ) {
				$this->phpcsFile->addWarning( 'Attempting a database schema change is discouraged.', $_pos, 'SchemaChange' );
			}
		}

		// Flag instance if not whitelisted.
		if ( ! $whitelisted_db_call ) {
			$this->phpcsFile->addWarning( 'Usage of a direct database call is discouraged.', $stackPtr, 'DirectQuery' );
		}

		if ( ! isset( $this->methods['cachable'][ $method ] ) ) {
			return $endOfStatement;
		}

		$whitelisted_cache = false;
		$cached            = false;
		$wp_cache_get      = false;
		if ( preg_match( '/cache\s+(?:ok|pass|clear|whitelist)/i', $endOfLineComment ) ) {
			$whitelisted_cache = true;
		}
		if ( ! $whitelisted_cache && ! empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
			$scope_function = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );

			if ( false === $scope_function ) {
				$scope_function = $this->phpcsFile->getCondition( $stackPtr, \T_CLOSURE );
			}

			if ( false !== $scope_function ) {
				$scopeStart = $this->tokens[ $scope_function ]['scope_opener'];
				$scopeEnd   = $this->tokens[ $scope_function ]['scope_closer'];

				for ( $i = ( $scopeStart + 1 ); $i < $scopeEnd; $i++ ) {
					if ( \T_STRING === $this->tokens[ $i ]['code'] ) {

						if ( isset( $this->cacheDeleteFunctions[ $this->tokens[ $i ]['content'] ] ) ) {

							if ( \in_array( $method, array( 'query', 'update', 'replace', 'delete' ), true ) ) {
								$cached = true;
								break;
							}
						} elseif ( isset( $this->cacheGetFunctions[ $this->tokens[ $i ]['content'] ] ) ) {

							$wp_cache_get = true;

						} elseif ( isset( $this->cacheSetFunctions[ $this->tokens[ $i ]['content'] ] ) ) {

							if ( $wp_cache_get ) {
								$cached = true;
								break;
							}
						}
					}
				}
			}
		}

		if ( ! $cached && ! $whitelisted_cache ) {
			$message = 'Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().';
			$this->phpcsFile->addWarning( $message, $stackPtr, 'NoCaching' );
		}

		return $endOfStatement;
	}

	/**
	 * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already.
	 *
	 * @since 0.11.0 Split out from the `process()` method.
	 *
	 * @return void
	 */
	protected function mergeFunctionLists() {
		if ( ! isset( $this->methods['all'] ) ) {
			$this->methods['all'] = array_merge( $this->methods['cachable'], $this->methods['noncachable'] );
		}

		if ( $this->customCacheGetFunctions !== $this->addedCustomFunctions['cacheget'] ) {
			$this->cacheGetFunctions = $this->merge_custom_array(
				$this->customCacheGetFunctions,
				$this->cacheGetFunctions
			);

			$this->addedCustomFunctions['cacheget'] = $this->customCacheGetFunctions;
		}

		if ( $this->customCacheSetFunctions !== $this->addedCustomFunctions['cacheset'] ) {
			$this->cacheSetFunctions = $this->merge_custom_array(
				$this->customCacheSetFunctions,
				$this->cacheSetFunctions
			);

			$this->addedCustomFunctions['cacheset'] = $this->customCacheSetFunctions;
		}

		if ( $this->customCacheDeleteFunctions !== $this->addedCustomFunctions['cachedelete'] ) {
			$this->cacheDeleteFunctions = $this->merge_custom_array(
				$this->customCacheDeleteFunctions,
				$this->cacheDeleteFunctions
			);

			$this->addedCustomFunctions['cachedelete'] = $this->customCacheDeleteFunctions;
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\PHP;

use WordPressCS\WordPress\Sniff;
use PHP_CodeSniffer\Util\Tokens;

/**
 * Enforces Yoda conditional statements.
 *
 * @link    https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#yoda-conditions
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.3.0
 * @since   0.12.0 This class now extends the WordPressCS native `Sniff` class.
 * @since   0.13.0 Class name changed: this class is now namespaced.
 */
class YodaConditionsSniff extends Sniff {

	/**
	 * The tokens that indicate the start of a condition.
	 *
	 * @since 0.12.0
	 *
	 * @var array
	 */
	protected $condition_start_tokens;

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {

		$starters                        = Tokens::$booleanOperators;
		$starters                       += Tokens::$assignmentTokens;
		$starters[ \T_CASE ]             = \T_CASE;
		$starters[ \T_RETURN ]           = \T_RETURN;
		$starters[ \T_INLINE_THEN ]      = \T_INLINE_THEN;
		$starters[ \T_INLINE_ELSE ]      = \T_INLINE_ELSE;
		$starters[ \T_SEMICOLON ]        = \T_SEMICOLON;
		$starters[ \T_OPEN_PARENTHESIS ] = \T_OPEN_PARENTHESIS;

		$this->condition_start_tokens = $starters;

		return array(
			\T_IS_EQUAL,
			\T_IS_NOT_EQUAL,
			\T_IS_IDENTICAL,
			\T_IS_NOT_IDENTICAL,
		);
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @param int $stackPtr The position of the current token in the stack.
	 *
	 * @return void
	 */
	public function process_token( $stackPtr ) {

		$start = $this->phpcsFile->findPrevious( $this->condition_start_tokens, $stackPtr, null, false, null, true );

		$needs_yoda = false;

		// Note: going backwards!
		for ( $i = $stackPtr; $i > $start; $i-- ) {

			// Ignore whitespace.
			if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) {
				continue;
			}

			// If this is a variable or array, we've seen all we need to see.
			if ( \T_VARIABLE === $this->tokens[ $i ]['code']
				|| \T_CLOSE_SQUARE_BRACKET === $this->tokens[ $i ]['code']
			) {
				$needs_yoda = true;
				break;
			}

			// If this is a function call or something, we are OK.
			if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] ) {
				return;
			}
		}

		if ( ! $needs_yoda ) {
			return;
		}

		// Check if this is a var to var comparison, e.g.: if ( $var1 == $var2 ).
		$next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );

		if ( isset( Tokens::$castTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) {
			$next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true );
		}

		if ( \in_array( $this->tokens[ $next_non_empty ]['code'], array( \T_SELF, \T_PARENT, \T_STATIC ), true ) ) {
			$next_non_empty = $this->phpcsFile->findNext(
				array_merge( Tokens::$emptyTokens, array( \T_DOUBLE_COLON ) ),
				( $next_non_empty + 1 ),
				null,
				true
			);
		}

		if ( \T_VARIABLE === $this->tokens[ $next_non_empty ]['code'] ) {
			return;
		}

		$this->phpcsFile->addError( 'Use Yoda Condition checks, you must.', $stackPtr, 'NotYoda' );
	}

}


<?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 WordPressCS\WordPress\Sniffs\WP;

use WordPressCS\WordPress\AbstractFunctionParameterSniff;

/**
 * Check for usage of deprecated parameters in WP functions and suggest alternative based on the parameter passed.
 *
 * This sniff will throw an error when usage of deprecated parameters is
 * detected if the parameter was deprecated before the minimum supported
 * WP version; a warning otherwise.
 * By default, it is set to presume that a project will support the current
 * WP version and up to three releases before.
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.12.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 * @since   0.14.0 Now has the ability to handle minimum supported WP version
 *                 being provided via the command-line or as as <config> value
 *                 in a custom ruleset.
 *
 * @uses    \WordPressCS\WordPress\Sniff::$minimum_supported_version
 */
class DeprecatedParametersSniff extends AbstractFunctionParameterSniff {

	/**
	 * The group name for this group of functions.
	 *
	 * @since 0.12.0
	 *
	 * @var string
	 */
	protected $group_name = 'wp_deprecated_parameters';

	/**
	 * Array of function, argument, and default value for deprecated argument.
	 *
	 * The functions are ordered alphabetically.
	 * Last updated for WordPress 4.8.0.
	 *
	 * @since 0.12.0
	 *
	 * @var array Multidimensional array with parameter details.
	 *    $target_functions = array(
	 *        (string) Function name. => array(
	 *            (int) Target parameter position, 1-based. => array(
	 *                'value'   => (mixed) Expected default value for the
	 *                              deprecated parameter. Currently the default
	 *                              values: true, false, null, empty arrays and
	 *                              both empty and non-empty strings can be
	 *                              handled correctly by the process_parameters()
	 *                              method. When an additional default value is
	 *                              added, the relevant code in the
	 *                              process_parameters() method will need to be
	 *                              adjusted.
	 *                'version' => (int) The WordPress version when deprecated.
	 *            )
	 *         )
	 *    );
	 */
	protected $target_functions = array(

		'add_option' => array(
			3 => array(
				'value'   => '',
				'version' => '2.3.0',
			),
		),
		'comments_link' => array(
			1 => array(
				'value'   => '',
				'version' => '0.72',
			),
			2 => array(
				'value'   => '',
				'version' => '1.3.0',
			),
		),
		'comments_number' => array(
			4 => array(
				'value'   => '',
				'version' => '1.3.0',
			),
		),
		'convert_chars' => array(
			2 => array(
				'value'   => '',
				'version' => '0.71',
			),
		),
		'discover_pingback_server_uri' => array(
			2 => array(
				'value'   => '',
				'version' => '2.7.0',
			),
		),
		'get_category_parents' => array(
			5 => array(
				'value'   => array(),
				'version' => '4.8.0',
			),
		),
		'get_delete_post_link' => array(
			2 => array(
				'value'   => '',
				'version' => '3.0.0',
			),
		),
		'get_last_updated' => array(
			1 => array(
				'value'   => '',
				'version' => '3.0.0', // Was previously part of MU.
			),
		),
		'get_the_author' => array(
			1 => array(
				'value'   => '',
				'version' => '2.1.0',
			),
		),
		'get_user_option' => array(
			3 => array(
				'value'   => '',
				'version' => '2.3.0',
			),
		),
		'get_wp_title_rss' => array(
			1 => array(
				'value'   => '–',
				'version' => '4.4.0',
			),
		),
		'is_email' => array(
			2 => array(
				'value'   => false,
				'version' => '3.0.0',
			),
		),
		'load_plugin_textdomain' => array(
			2 => array(
				'value'   => false,
				'version' => '2.7.0',
			),
		),
		'safecss_filter_attr' => array(
			2 => array(
				'value'   => '',
				'version' => '2.8.1',
			),
		),
		'the_attachment_link' => array(
			3 => array(
				'value'   => false,
				'version' => '2.5.0',
			),
		),
		'the_author' => array(
			1 => array(
				'value'   => '',
				'version' => '2.1.0',
			),
			2 => array(
				'value'   => true,
				'version' => '1.5.0',
			),
		),
		'the_author_posts_link' => array(
			1 => array(
				'value'   => '',
				'version' => '2.1.0',
			),
		),
		'trackback_rdf' => array(
			1 => array(
				'value'   => '',
				'version' => '2.5.0',
			),
		),
		'trackback_url' => array(
			1 => array(
				'value'   => true,
				'version' => '2.5.0',
			),
		),
		'update_blog_option' => array(
			4 => array(
				'value'   => null,
				'version' => '3.1.0',
			),
		),
		'update_blog_status' => array(
			4 => array(
				'value'   => null,
				'version' => '3.1.0',
			),
		),
		'update_user_status' => array(
			4 => array(
				'value'   => null,
				'version' => '3.0.2',
			),
		),
		'unregister_setting' => array(
			4 => array(
				'value'   => '',
				'version' => '4.7.0',
			),
		),
		'wp_get_http_headers' => array(
			2 => array(
				'value'   => false,
				'version' => '2.7.0',
			),
		),
		'wp_get_sidebars_widgets' => array(
			1 => array(
				'value'   => true,
				'version' => '2.8.1',
			),
		),
		'wp_install' => array(
			5 => array(
				'value'   => '',
				'version' => '2.6.0',
			),
		),
		'wp_new_user_notification' => array(
			2 => array(
				'value'   => null,
				'version' => '4.3.1',
			),
		),
		'wp_notify_postauthor' => array(
			2 => array(
				'value'   => null,
				'version' => '3.8.0',
			),
		),
		'wp_title_rss' => array(
			1 => array(
				'value'   => '–',
				'version' => '4.4.0',
			),
		),
		'wp_upload_bits' => array(
			2 => array(
				'value'   => null,
				'version' => '2.0.0',
			),
		),
		'xfn_check' => array(
			3 => array(
				'value'   => '',
				'version' => '2.5.0',
			),
		),
	);

	/**
	 * Process the parameters of a matched function.
	 *
	 * @since 0.12.0
	 *
	 * @param int    $stackPtr        The position of the current token in the stack.
	 * @param 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 ) {

		$this->get_wp_version_from_cl();

		$paramCount = \count( $parameters );
		foreach ( $this->target_functions[ $matched_content ] as $position => $parameter_args ) {

			// Check that number of parameters defined is not less than the position to check.
			if ( $position > $paramCount ) {
				break;
			}

			// The list will need to updated if the default value is not supported.
			switch ( $parameters[ $position ]['raw'] ) {
				case 'true':
					$matched_parameter = true;
					break;
				case 'false':
					$matched_parameter = false;
					break;
				case 'null':
					$matched_parameter = null;
					break;
				case 'array()':
				case '[]':
					$matched_parameter = array();
					break;
				default:
					$matched_parameter = $this->strip_quotes( $parameters[ $position ]['raw'] );
					break;
			}

			if ( $parameter_args['value'] === $matched_parameter ) {
				continue;
			}

			$message  = 'The parameter "%s" at position #%s of %s() has been deprecated since WordPress version %s.';
			$is_error = version_compare( $parameter_args['version'], $this->minimum_supported_version, '<' );
			$code     = $this->string_to_errorcode( ucfirst( $matched_content ) . 'Param' . $position . 'Found' );

			$data = array(
				$parameters[ $position ]['raw'],
				$position,
				$matched_content,
				$parameter_args['version'],
			);

			if ( isset( $parameter_args['value'] ) && $position < $paramCount ) {
				$message .= ' Use "%s" instead.';
				$data[]   = (string) $parameter_args['value'];
			} else {
				$message .= ' Instead do not pass the parameter.';
			}

			$this->addMessage( $message, $stackPtr, $is_error, $code, $data, 0 );
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\Security;

use WordPressCS\WordPress\Sniff;

/**
 * Checks that nonce verification accompanies form processing.
 *
 * @link    https://developer.wordpress.org/plugins/security/nonces/ Nonces on Plugin Developer Handbook
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.5.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 * @since   1.0.0  This sniff has been moved from the `CSRF` category to the `Security` category.
 */
class NonceVerificationSniff extends Sniff {

	/**
	 * Superglobals to notify about when not accompanied by an nonce check.
	 *
	 * A value of `true` results in an error. A value of `false` in a warning.
	 *
	 * @since 0.12.0
	 *
	 * @var array
	 */
	protected $superglobals = array(
		'$_POST'    => true,
		'$_FILE'    => true,
		'$_GET'     => false,
		'$_REQUEST' => false,
	);

	/**
	 * Custom list of functions which verify nonces.
	 *
	 * @since 0.5.0
	 *
	 * @var string|string[]
	 */
	public $customNonceVerificationFunctions = array();

	/**
	 * Custom list of functions that sanitize the values passed to them.
	 *
	 * @since 0.11.0
	 *
	 * @var string|string[]
	 */
	public $customSanitizingFunctions = array();

	/**
	 * Custom sanitizing functions that implicitly unslash the values passed to them.
	 *
	 * @since 0.11.0
	 *
	 * @var string|string[]
	 */
	public $customUnslashingSanitizingFunctions = array();

	/**
	 * Cache of previously added custom functions.
	 *
	 * Prevents having to do the same merges over and over again.
	 *
	 * @since 0.5.0
	 * @since 0.11.0 - Changed from public static to protected non-static.
	 *               - Changed the format from simple bool to array.
	 *
	 * @var array
	 */
	protected $addedCustomFunctions = array(
		'nonce'           => array(),
		'sanitize'        => array(),
		'unslashsanitize' => array(),
	);

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {

		return array(
			\T_VARIABLE,
		);
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @param int $stackPtr The position of the current token in the stack.
	 *
	 * @return void
	 */
	public function process_token( $stackPtr ) {

		$instance = $this->tokens[ $stackPtr ];

		if ( ! isset( $this->superglobals[ $instance['content'] ] ) ) {
			return;
		}

		if ( $this->has_whitelist_comment( 'CSRF', $stackPtr ) ) {
			return;
		}

		if ( $this->is_assignment( $stackPtr ) ) {
			return;
		}

		$this->mergeFunctionLists();

		if ( $this->has_nonce_check( $stackPtr ) ) {
			return;
		}

		$error_code = 'Missing';
		if ( false === $this->superglobals[ $instance['content'] ] ) {
			$error_code = 'Recommended';
		}

		// If we're still here, no nonce-verification function was found.
		$this->addMessage(
			'Processing form data without nonce verification.',
			$stackPtr,
			$this->superglobals[ $instance['content'] ],
			$error_code
		);
	}

	/**
	 * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already.
	 *
	 * @since 0.11.0 Split out from the `process()` method.
	 *
	 * @return void
	 */
	protected function mergeFunctionLists() {
		if ( $this->customNonceVerificationFunctions !== $this->addedCustomFunctions['nonce'] ) {
			$this->nonceVerificationFunctions = $this->merge_custom_array(
				$this->customNonceVerificationFunctions,
				$this->nonceVerificationFunctions
			);

			$this->addedCustomFunctions['nonce'] = $this->customNonceVerificationFunctions;
		}

		if ( $this->customSanitizingFunctions !== $this->addedCustomFunctions['sanitize'] ) {
			$this->sanitizingFunctions = $this->merge_custom_array(
				$this->customSanitizingFunctions,
				$this->sanitizingFunctions
			);

			$this->addedCustomFunctions['sanitize'] = $this->customSanitizingFunctions;
		}

		if ( $this->customUnslashingSanitizingFunctions !== $this->addedCustomFunctions['unslashsanitize'] ) {
			$this->unslashingSanitizingFunctions = $this->merge_custom_array(
				$this->customUnslashingSanitizingFunctions,
				$this->unslashingSanitizingFunctions
			);

			$this->addedCustomFunctions['unslashsanitize'] = $this->customUnslashingSanitizingFunctions;
		}
	}

}


<?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 WordPressCS\WordPress;

use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;

/**
 * Advises about parameters used in function calls.
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.11.0
 */
abstract class AbstractFunctionParameterSniff extends AbstractFunctionRestrictionsSniff {

	/**
	 * The group name for this group of functions.
	 *
	 * Intended to be overruled in the child class.
	 *
	 * @var string
	 */
	protected $group_name = 'restricted_parameters';

	/**
	 * Functions this sniff is looking for. Should be defined in the child class.
	 *
	 * @var array The only requirement for this array is that the top level
	 *            array keys are the names of the functions you're looking for.
	 *            Other than that, the array can have arbitrary content
	 *            depending on your needs.
	 */
	protected $target_functions = array();

	/**
	 * Groups of function to restrict.
	 *
	 * @return array
	 */
	public function getGroups() {
		if ( empty( $this->target_functions ) ) {
			return array();
		}

		return array(
			$this->group_name => array(
				'functions' => array_keys( $this->target_functions ),
			),
		);
	}

	/**
	 * Process a matched token.
	 *
	 * @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.
	 *
	 * @return int|void Integer stack pointer to skip forward or void to continue
	 *                  normal file processing.
	 */
	public function process_matched_token( $stackPtr, $group_name, $matched_content ) {

		$parameters = $this->get_function_call_parameters( $stackPtr );

		if ( empty( $parameters ) ) {
			return $this->process_no_parameters( $stackPtr, $group_name, $matched_content );
		} else {
			return $this->process_parameters( $stackPtr, $group_name, $matched_content, $parameters );
		}
	}

	/**
	 * Process the parameters of a matched function.
	 *
	 * This method has to be made concrete in child classes.
	 *
	 * @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 int|void Integer stack pointer to skip forward or void to continue
	 *                  normal file processing.
	 */
	abstract public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters );

	/**
	 * Process the function if no parameters were found.
	 *
	 * Defaults to doing nothing. Can be overloaded in child classes to handle functions
	 * were parameters are expected, but none found.
	 *
	 * @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.
	 *
	 * @return int|void Integer stack pointer to skip forward or void to continue
	 *                  normal file processing.
	 */
	public function process_no_parameters( $stackPtr, $group_name, $matched_content ) {
		return;
	}

}


<?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 WordPressCS\WordPress\Sniffs\NamingConventions;

use PHP_CodeSniffer\Sniffs\AbstractVariableSniff as PHPCS_AbstractVariableSniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use WordPressCS\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.
 * @since   2.0.0  - Defers to the upstream `$phpReservedVars` property.
 *                 - Now offers name suggestions for variables in violation.
 *
 * 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
 *
 * @uses PHP_CodeSniffer\Sniffs\AbstractVariableSniff::$phpReservedVars
 */
class ValidVariableNameSniff extends PHPCS_AbstractVariableSniff {

	/**
	 * 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,
	);

	/**
	 * 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->phpReservedVars[ $var_name ] ) ) {
			return;
		}

		// Merge any custom variables with the defaults.
		$this->mergeWhiteList();

		// 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, try "$%s"';
						$data  = array(
							$original_var_name,
							Sniff::get_snake_case_name_suggestion( $original_var_name ),
						);
						$phpcs_file->addError( $error, $var, 'UsedPropertyNotSnakeCase', $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, try "$%s"';
				$error_name = 'UsedPropertyNotSnakeCase';
			} elseif ( ! $in_class ) {
				$error      = 'Variable "$%s" is not in valid snake_case format, try "$%s"';
				$error_name = 'VariableNotSnakeCase';
			}

			if ( isset( $error, $error_name ) ) {
				$data = array(
					$original_var_name,
					Sniff::get_snake_case_name_suggestion( $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();

		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, try "$%s"';
			$data  = array(
				$var_name,
				Sniff::get_snake_case_name_suggestion( $var_name ),
			);
			$phpcs_file->addError( $error, $stack_ptr, 'PropertyNotSnakeCase', $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();

			foreach ( $matches[1] as $var_name ) {
				// If it's a php reserved var, then its ok.
				if ( isset( $this->phpReservedVars[ $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, try "$%s"';
					$data  = array(
						$var_name,
						Sniff::get_snake_case_name_suggestion( $var_name ),
					);
					$phpcs_file->addError( $error, $stack_ptr, 'InterpolatedVariableNotSnakeCase', $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
	 * @since 2.0.0  Removed unused $phpcs_file parameter.
	 *
	 * @return void
	 */
	protected function mergeWhiteList() {
		if ( $this->customPropertiesWhitelist !== $this->addedCustomProperties['properties'] ) {
			// Fix property potentially passed as comma-delimited string.
			$customProperties = Sniff::merge_custom_array( $this->customPropertiesWhitelist, array(), false );

			$this->whitelisted_mixed_case_member_var_names = Sniff::merge_custom_array(
				$customProperties,
				$this->whitelisted_mixed_case_member_var_names
			);

			$this->addedCustomProperties['properties'] = $this->customPropertiesWhitelist;
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\Security;

use WordPressCS\WordPress\AbstractFunctionParameterSniff;

/**
 * Warn about __FILE__ for page registration.
 *
 * @link    https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#using-__file__-for-page-registration
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.3.0
 * @since   0.11.0 Refactored to extend the new WordPressCS native
 *                 `AbstractFunctionParameterSniff` class.
 * @since   0.13.0 Class name changed: this class is now namespaced.
 * @since   1.0.0  This sniff has been moved from the `VIP` category to the `Security` category.
 */
class PluginMenuSlugSniff extends AbstractFunctionParameterSniff {

	/**
	 * The group name for this group of functions.
	 *
	 * @since 0.11.0
	 *
	 * @var string
	 */
	protected $group_name = 'add_menu_functions';

	/**
	 * Functions which can be used to add pages to the WP Admin menu.
	 *
	 * @since 0.3.0
	 * @since 0.11.0 Renamed from $add_menu_functions to $target_functions
	 *               and changed visibility to protected.
	 *
	 * @var array <string function name> => <array target parameter positions>
	 */
	protected $target_functions = array(
		'add_menu_page'       => array( 4 ),
		'add_object_page'     => array( 4 ),
		'add_utility_page'    => array( 4 ),
		'add_submenu_page'    => array( 1, 5 ),
		'add_dashboard_page'  => array( 4 ),
		'add_posts_page'      => array( 4 ),
		'add_media_page'      => array( 4 ),
		'add_links_page'      => array( 4 ),
		'add_pages_page'      => array( 4 ),
		'add_comments_page'   => array( 4 ),
		'add_theme_page'      => array( 4 ),
		'add_plugins_page'    => array( 4 ),
		'add_users_page'      => array( 4 ),
		'add_management_page' => array( 4 ),
		'add_options_page'    => array( 4 ),
	);

	/**
	 * 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 ) {
		foreach ( $this->target_functions[ $matched_content ] as $position ) {
			if ( isset( $parameters[ $position ] ) ) {
				$file_constant = $this->phpcsFile->findNext( \T_FILE, $parameters[ $position ]['start'], ( $parameters[ $position ]['end'] + 1 ) );

				if ( false !== $file_constant ) {
					$this->phpcsFile->addWarning( 'Using __FILE__ for menu slugs risks exposing filesystem structure.', $stackPtr, 'Using__FILE__' );
				}
			}
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\Arrays;

use WordPressCS\WordPress\Sniff;
use WordPressCS\WordPress\PHPCSHelper;
use PHP_CodeSniffer\Util\Tokens;

/**
 * Enforces WordPress array indentation for multi-line arrays.
 *
 * @link    https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.12.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 *
 * {@internal This sniff should eventually be pulled upstream as part of a solution
 * for https://github.com/squizlabs/PHP_CodeSniffer/issues/582 }}
 */
class ArrayIndentationSniff extends Sniff {

	/**
	 * Should tabs be used for indenting?
	 *
	 * If TRUE, fixes will be made using tabs instead of spaces.
	 * The size of each tab is important, so it should be specified
	 * using the --tab-width CLI argument.
	 *
	 * {@internal While for WPCS this should always be `true`, this property
	 * was added in anticipation of upstreaming the sniff.
	 * This property is the same as used in `Generic.WhiteSpace.ScopeIndent`.}}
	 *
	 * @var bool
	 */
	public $tabIndent = true;

	/**
	 * The --tab-width CLI value that is being used.
	 *
	 * @var int
	 */
	private $tab_width;

	/**
	 * Tokens to ignore for subsequent lines in a multi-line array item.
	 *
	 * Property is set in the register() method.
	 *
	 * @var array
	 */
	private $ignore_tokens = array();

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {
		/*
		 * Set the $ignore_tokens property.
		 *
		 * Existing heredoc, nowdoc and inline HTML indentation should be respected at all times.
		 */
		$this->ignore_tokens = Tokens::$heredocTokens;
		unset( $this->ignore_tokens[ \T_START_HEREDOC ], $this->ignore_tokens[ \T_START_NOWDOC ] );
		$this->ignore_tokens[ \T_INLINE_HTML ] = \T_INLINE_HTML;

		return array(
			\T_ARRAY,
			\T_OPEN_SHORT_ARRAY,
		);
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @param int $stackPtr The position of the current token in the stack.
	 *
	 * @return void
	 */
	public function process_token( $stackPtr ) {
		if ( ! isset( $this->tab_width ) ) {
			$this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile );
		}

		/*
		 * Determine the array opener & closer.
		 */
		$array_open_close = $this->find_array_open_close( $stackPtr );
		if ( false === $array_open_close ) {
			// Array open/close could not be determined.
			return;
		}

		$opener = $array_open_close['opener'];
		$closer = $array_open_close['closer'];

		if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) {
			// Not interested in single line arrays.
			return;
		}

		/*
		 * Check the closing bracket is lined up with the start of the content on the line
		 * containing the array opener.
		 */
		$opener_line_spaces = $this->get_indentation_size( $opener );
		$closer_line_spaces = ( $this->tokens[ $closer ]['column'] - 1 );

		if ( $closer_line_spaces !== $opener_line_spaces ) {
			$error      = 'Array closer not aligned correctly; expected %s space(s) but found %s';
			$error_code = 'CloseBraceNotAligned';

			/*
			 * Report & fix the issue if the close brace is on its own line with
			 * nothing or only indentation whitespace before it.
			 */
			if ( 0 === $closer_line_spaces
				|| ( \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code']
					&& 1 === $this->tokens[ ( $closer - 1 ) ]['column'] )
			) {
				$this->add_array_alignment_error(
					$closer,
					$error,
					$error_code,
					$opener_line_spaces,
					$closer_line_spaces,
					$this->get_indentation_string( $opener_line_spaces )
				);
			} else {
				/*
				 * Otherwise, only report the error, don't try and fix it (yet).
				 *
				 * It will get corrected in a future loop of the fixer once the closer
				 * has been moved to its own line by the `ArrayDeclarationSpacing` sniff.
				 */
				$this->phpcsFile->addError(
					$error,
					$closer,
					$error_code,
					array( $opener_line_spaces, $closer_line_spaces )
				);
			}

			unset( $error, $error_code );
		}

		/*
		 * Verify & correct the array item indentation.
		 */
		$array_items = $this->get_function_call_parameters( $stackPtr );
		if ( empty( $array_items ) ) {
			// Strange, no array items found.
			return;
		}

		$expected_spaces      = ( $opener_line_spaces + $this->tab_width );
		$expected_indent      = $this->get_indentation_string( $expected_spaces );
		$end_of_previous_item = $opener;

		foreach ( $array_items as $item ) {
			$end_of_this_item = ( $item['end'] + 1 );

			// Find the line on which the item starts.
			$first_content = $this->phpcsFile->findNext(
				array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
				$item['start'],
				$end_of_this_item,
				true
			);

			// Deal with trailing comments.
			if ( false !== $first_content
				&& \T_COMMENT === $this->tokens[ $first_content ]['code']
				&& $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line']
			) {
				$first_content = $this->phpcsFile->findNext(
					array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE, \T_COMMENT ),
					( $first_content + 1 ),
					$end_of_this_item,
					true
				);
			}

			if ( false === $first_content ) {
				$end_of_previous_item = $end_of_this_item;
				continue;
			}

			// Bow out from reporting and fixing mixed multi-line/single-line arrays.
			// That is handled by the ArrayDeclarationSpacingSniff.
			if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line']
				|| ( 1 !== $this->tokens[ $first_content ]['column']
					&& \T_WHITESPACE !== $this->tokens[ ( $first_content - 1 ) ]['code'] )
			) {
				return $closer;
			}

			$found_spaces = ( $this->tokens[ $first_content ]['column'] - 1 );

			if ( $found_spaces !== $expected_spaces ) {
				$this->add_array_alignment_error(
					$first_content,
					'Array item not aligned correctly; expected %s spaces but found %s',
					'ItemNotAligned',
					$expected_spaces,
					$found_spaces,
					$expected_indent
				);
			}

			// No need for further checking if this is a one-line array item.
			if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $item['end'] ]['line'] ) {
				$end_of_previous_item = $end_of_this_item;
				continue;
			}

			/*
			 * Multi-line array items.
			 *
			 * Verify & if needed, correct the indentation of subsequent lines.
			 * Subsequent lines may be indented more or less than the mimimum expected indent,
			 * but the "first line after" should be indented - at least - as much as the very first line
			 * of the array item.
			 * Indentation correction for subsequent lines will be based on that diff.
			 */

			// Find first token on second line of the array item.
			// If the second line is a heredoc/nowdoc, continue on until we find a line with a different token.
			// Same for the second line of a multi-line text string.
			for ( $ptr = ( $first_content + 1 ); $ptr <= $item['end']; $ptr++ ) {
				if ( $this->tokens[ $first_content ]['line'] !== $this->tokens[ $ptr ]['line']
					&& 1 === $this->tokens[ $ptr ]['column']
					&& false === $this->ignore_token( $ptr )
				) {
					break;
				}
			}

			$first_content_on_line2 = $this->phpcsFile->findNext(
				array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
				$ptr,
				$end_of_this_item,
				true
			);

			if ( false === $first_content_on_line2 ) {
				/*
				 * Apparently there were only tokens in the ignore list on subsequent lines.
				 *
				 * In that case, the comma after the array item might be on a line by itself,
				 * so check its placement.
				 */
				if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line']
					&& \T_COMMA === $this->tokens[ $end_of_this_item ]['code']
					&& ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces
				) {
					$this->add_array_alignment_error(
						$end_of_this_item,
						'Comma after multi-line array item not aligned correctly; expected %s spaces, but found %s',
						'MultiLineArrayItemCommaNotAligned',
						$expected_spaces,
						( $this->tokens[ $end_of_this_item ]['column'] - 1 ),
						$expected_indent
					);
				}

				$end_of_previous_item = $end_of_this_item;
				continue;
			}

			$found_spaces_on_line2    = $this->get_indentation_size( $first_content_on_line2 );
			$expected_spaces_on_line2 = $expected_spaces;

			if ( $found_spaces < $found_spaces_on_line2 ) {
				$expected_spaces_on_line2 += ( $found_spaces_on_line2 - $found_spaces );
			}

			if ( $found_spaces_on_line2 !== $expected_spaces_on_line2 ) {

				$fix = $this->phpcsFile->addFixableError(
					'Multi-line array item not aligned correctly; expected %s spaces, but found %s',
					$first_content_on_line2,
					'MultiLineArrayItemNotAligned',
					array(
						$expected_spaces_on_line2,
						$found_spaces_on_line2,
					)
				);

				if ( true === $fix ) {
					$expected_indent_on_line2 = $this->get_indentation_string( $expected_spaces_on_line2 );

					$this->phpcsFile->fixer->beginChangeset();

					// Fix second line for the array item.
					if ( 1 === $this->tokens[ $first_content_on_line2 ]['column']
						&& \T_COMMENT === $this->tokens[ $first_content_on_line2 ]['code']
					) {
						$actual_comment = ltrim( $this->tokens[ $first_content_on_line2 ]['content'] );
						$replacement    = $expected_indent_on_line2 . $actual_comment;

						$this->phpcsFile->fixer->replaceToken( $first_content_on_line2, $replacement );

					} else {
						$this->fix_alignment_error( $first_content_on_line2, $expected_indent_on_line2 );
					}

					// Fix subsequent lines.
					for ( $i = ( $first_content_on_line2 + 1 ); $i <= $item['end']; $i++ ) {
						// We're only interested in the first token on each line.
						if ( 1 !== $this->tokens[ $i ]['column'] ) {
							if ( $this->tokens[ $i ]['line'] === $this->tokens[ $item['end'] ]['line'] ) {
								// We might as well quit if we're past the first token on the last line.
								break;
							}
							continue;
						}

						$first_content_on_line = $this->phpcsFile->findNext(
							array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
							$i,
							$end_of_this_item,
							true
						);

						if ( false === $first_content_on_line ) {
							break;
						}

						// Ignore lines with heredoc and nowdoc tokens and subsequent lines in multi-line strings.
						if ( true === $this->ignore_token( $first_content_on_line ) ) {
							$i = $first_content_on_line;
							continue;
						}

						$found_spaces_on_line    = $this->get_indentation_size( $first_content_on_line );
						$expected_spaces_on_line = ( $expected_spaces_on_line2 + ( $found_spaces_on_line - $found_spaces_on_line2 ) );
						$expected_spaces_on_line = max( $expected_spaces_on_line, 0 ); // Can't be below 0.
						$expected_indent_on_line = $this->get_indentation_string( $expected_spaces_on_line );

						if ( $found_spaces_on_line !== $expected_spaces_on_line ) {
							if ( 1 === $this->tokens[ $first_content_on_line ]['column']
								&& \T_COMMENT === $this->tokens[ $first_content_on_line ]['code']
							) {
								$actual_comment = ltrim( $this->tokens[ $first_content_on_line ]['content'] );
								$replacement    = $expected_indent_on_line . $actual_comment;

								$this->phpcsFile->fixer->replaceToken( $first_content_on_line, $replacement );
							} else {
								$this->fix_alignment_error( $first_content_on_line, $expected_indent_on_line );
							}
						}

						// Move past any potential empty lines between the previous non-empty line and this one.
						// No need to do the fixes twice.
						$i = $first_content_on_line;
					}

					/*
					 * Check the placement of the comma after the array item as it might be on a line by itself.
					 */
					if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line']
						&& \T_COMMA === $this->tokens[ $end_of_this_item ]['code']
						&& ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces
					) {
						$this->add_array_alignment_error(
							$end_of_this_item,
							'Comma after array item not aligned correctly; expected %s spaces, but found %s',
							'MultiLineArrayItemCommaNotAligned',
							$expected_spaces,
							( $this->tokens[ $end_of_this_item ]['column'] - 1 ),
							$expected_indent
						);
					}

					$this->phpcsFile->fixer->endChangeset();
				}
			}

			$end_of_previous_item = $end_of_this_item;
		}
	}

	/**
	 * Should the token be ignored ?
	 *
	 * This method is only intended to be used with the first token on a line
	 * for subsequent lines in an multi-line array item.
	 *
	 * @param int $ptr Stack pointer to the first token on a line.
	 *
	 * @return bool
	 */
	protected function ignore_token( $ptr ) {
		$token_code = $this->tokens[ $ptr ]['code'];

		if ( isset( $this->ignore_tokens[ $token_code ] ) ) {
			return true;
		}

		/*
		 * If it's a subsequent line of a multi-line sting, it will not start with a quote
		 * character, nor just *be* a quote character.
		 */
		if ( \T_CONSTANT_ENCAPSED_STRING === $token_code
			|| \T_DOUBLE_QUOTED_STRING === $token_code
		) {
			// Deal with closing quote of a multi-line string being on its own line.
			if ( "'" === $this->tokens[ $ptr ]['content']
				|| '"' === $this->tokens[ $ptr ]['content']
			) {
				return true;
			}

			// Deal with subsequent lines of a multi-line string where the token is broken up per line.
			if ( "'" !== $this->tokens[ $ptr ]['content'][0]
				&& '"' !== $this->tokens[ $ptr ]['content'][0]
			) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Determine the line indentation whitespace.
	 *
	 * @param int $ptr Stack pointer to an arbitrary token on a line.
	 *
	 * @return int Nr of spaces found. Where necessary, tabs are translated to spaces.
	 */
	protected function get_indentation_size( $ptr ) {

		// Find the first token on the line.
		for ( ; $ptr >= 0; $ptr-- ) {
			if ( 1 === $this->tokens[ $ptr ]['column'] ) {
				break;
			}
		}

		$whitespace = '';

		if ( \T_WHITESPACE === $this->tokens[ $ptr ]['code']
			|| \T_DOC_COMMENT_WHITESPACE === $this->tokens[ $ptr ]['code']
		) {
			return $this->tokens[ $ptr ]['length'];
		}

		/*
		 * Special case for multi-line, non-docblock comments.
		 * Only applicable for subsequent lines in an array item.
		 *
		 * First/Single line is tokenized as T_WHITESPACE + T_COMMENT
		 * Subsequent lines are tokenized as T_COMMENT including the indentation whitespace.
		 */
		if ( \T_COMMENT === $this->tokens[ $ptr ]['code'] ) {
			$content        = $this->tokens[ $ptr ]['content'];
			$actual_comment = ltrim( $content );
			$whitespace     = str_replace( $actual_comment, '', $content );
		}

		return \strlen( $whitespace );
	}

	/**
	 * Create an indentation string.
	 *
	 * @param int $nr Number of spaces the indentation should be.
	 *
	 * @return string
	 */
	protected function get_indentation_string( $nr ) {
		if ( 0 >= $nr ) {
			return '';
		}

		// Space-based indentation.
		if ( false === $this->tabIndent ) {
			return str_repeat( ' ', $nr );
		}

		// Tab-based indentation.
		$num_tabs    = (int) floor( $nr / $this->tab_width );
		$remaining   = ( $nr % $this->tab_width );
		$tab_indent  = str_repeat( "\t", $num_tabs );
		$tab_indent .= str_repeat( ' ', $remaining );

		return $tab_indent;
	}

	/**
	 * Throw an error and fix incorrect array alignment.
	 *
	 * @param int    $ptr        Stack pointer to the first content on the line.
	 * @param string $error      Error message.
	 * @param string $error_code Error code.
	 * @param int    $expected   Expected nr of spaces (tabs translated to space value).
	 * @param int    $found      Found nr of spaces (tabs translated to space value).
	 * @param string $new_indent Whitespace indent replacement content.
	 */
	protected function add_array_alignment_error( $ptr, $error, $error_code, $expected, $found, $new_indent ) {

		$fix = $this->phpcsFile->addFixableError( $error, $ptr, $error_code, array( $expected, $found ) );
		if ( true === $fix ) {
			$this->fix_alignment_error( $ptr, $new_indent );
		}
	}

	/**
	 * Fix incorrect array alignment.
	 *
	 * @param int    $ptr        Stack pointer to the first content on the line.
	 * @param string $new_indent Whitespace indent replacement content.
	 */
	protected function fix_alignment_error( $ptr, $new_indent ) {
		if ( 1 === $this->tokens[ $ptr ]['column'] ) {
			$this->phpcsFile->fixer->addContentBefore( $ptr, $new_indent );
		} else {
			$this->phpcsFile->fixer->replaceToken( ( $ptr - 1 ), $new_indent );
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\PHP;

use WordPressCS\WordPress\Sniff;

/**
 * Enforces Strict Comparison checks, based upon Squiz code.
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since   0.4.0
 * @since   0.13.0 Class name changed: this class is now namespaced.
 *
 * Last synced with base class ?[unknown date]? at commit ?[unknown commit]?.
 * It is currently unclear whether this sniff is actually based on Squiz code on whether the above
 * reference to it is a copy/paste oversight.
 * @link    Possibly: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php
 */
class StrictComparisonsSniff extends Sniff {

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @return array
	 */
	public function register() {
		return array(
			\T_IS_EQUAL,
			\T_IS_NOT_EQUAL,
		);
	}

	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @param int $stackPtr The position of the current token in the stack.
	 *
	 * @return void
	 */
	public function process_token( $stackPtr ) {

		if ( ! $this->has_whitelist_comment( 'loose comparison', $stackPtr ) ) {
			$error = 'Found: ' . $this->tokens[ $stackPtr ]['content'] . '. Use strict comparisons (=== or !==).';
			$this->phpcsFile->addWarning( $error, $stackPtr, 'LooseComparison' );
		}
	}

}


<?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 WordPressCS\WordPress\Sniffs\WP;

use WordPressCS\WordPress\AbstractFunctionParameterSniff;
use PHP_CodeSniffer\Util\Tokens;

/**
 * This checks the enqueued 4th and 5th parameters to make sure the version and in_footer are set.
 *
 * If a source ($src) value is passed, then version ($ver) needs to have non-falsy value.
 * If a source ($src) value is passed a check for in footer ($in_footer), warn the user if the value is falsy.
 *
 * @link https://developer.wordpress.org/reference/functions/wp_register_script/
 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/
 * @link https://developer.wordpress.org/reference/functions/wp_register_style/
 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_style/
 *
 * @package WPCS\WordPressCodingStandards
 *
 * @since 1.0.0
 */
class EnqueuedResourceParametersSniff extends AbstractFunctionParameterSniff {

	/**
	 * The group name for this group of functions.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	protected $group_name = 'Enqueued';

	/**
	 * List of enqueued functions that need to be checked for use of the in_footer and version arguments.
	 *
	 * @since 1.0.0
	 *
	 * @var array <string function_name> => <bool true>
	 */
	protected $target_functions = array(
		'wp_register_script' => true,
		'wp_enqueue_script'  => true,
		'wp_register_style'  => true,
		'wp_enqueue_style'   => true,
	);

	/**
	 * False + the empty tokens array.
	 *
	 * This array is enriched with the $emptyTokens array in the register() method.
	 *
	 * @var array
	 */
	private $false_tokens = array(
		\T_FALSE => \T_FALSE,
	);

	/**
	 * Token codes which are "safe" to accept to determine whether a version would evaluate to `false`.
	 *
	 * This array is enriched with the several of the PHPCS token arrays in the register() method.
	 *
	 * @var array
	 */
	private $safe_tokens = array(
		\T_NULL                     => \T_NULL,
		\T_FALSE                    => \T_FALSE,
		\T_TRUE                     => \T_TRUE,
		\T_LNUMBER                  => \T_LNUMBER,
		\T_DNUMBER                  => \T_DNUMBER,
		\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
		\T_START_NOWDOC             => \T_START_NOWDOC,
		\T_NOWDOC                   => \T_NOWDOC,
		\T_END_NOWDOC               => \T_END_NOWDOC,
		\T_OPEN_PARENTHESIS         => \T_OPEN_PARENTHESIS,
		\T_CLOSE_PARENTHESIS        => \T_CLOSE_PARENTHESIS,
		\T_STRING_CONCAT            => \T_STRING_CONCAT,
	);

	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * Overloads and calls the parent method to allow for adding additional tokens to the $safe_tokens property.
	 *
	 * @return array
	 */
	public function register() {
		$this->false_tokens += Tokens::$emptyTokens;

		$this->safe_tokens += Tokens::$emptyTokens;
		$this->safe_tokens += Tokens::$assignmentTokens;
		$this->safe_tokens += Tokens::$comparisonTokens;
		$this->safe_tokens += Tokens::$operators;
		$this->safe_tokens += Tokens::$booleanOperators;
		$this->safe_tokens += Tokens::$castTokens;

		return parent::register();
	}

	/**
	 * Process the parameters of a matched function.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $stackPtr        The position of the current token in the stack.
	 * @param 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 ) {

		// Check to see if a source ($src) is specified.
		if ( ! isset( $parameters[2] ) ) {
			return;
		}

		/*
		 * Version Check: Check to make sure the version is set explicitly.
		 */

		if ( ! isset( $parameters[4] ) || 'null' === $parameters[4]['raw'] ) {
			$type = 'script';
			if ( strpos( $matched_content, '_style' ) !== false ) {
				$type = 'style';
			}

			$this->phpcsFile->addError(
				'Resource version not set in call to %s(). This means new versions of the %s will not always be loaded due to browser caching.',
				$stackPtr,
				'MissingVersion',
				array( $matched_content, $type )
			);
		} else {
			// The version argument should have a non-false value.
			if ( $this->is_falsy( $parameters[4]['start'], $parameters[4]['end'] ) ) {
				$this->phpcsFile->addError(
					'Version parameter is not explicitly set or has been set to an equivalent of "false" for %s; ' .
					'This means that the WordPress core version will be used which is not recommended for plugin or theme development.',
					$stackPtr,
					'NoExplicitVersion',
					array( $matched_content )
				);
			}
		}

		/*
		 * In footer Check
		 *
		 * Check to make sure that $in_footer is set to true.
		 * It will warn the user to make sure it is intended.
		 *
		 * Only wp_register_script and wp_enqueue_script need this check,
		 * as this parameter is not available to wp_register_style and wp_enqueue_style.
		 */
		if ( 'wp_register_script' !== $matched_content && 'wp_enqueue_script' !== $matched_content ) {
			return;
		}

		if ( ! isset( $parameters[5] ) ) {
			// If in footer is not set, throw a warning about the default.
			$this->phpcsFile->addWarning(
				'In footer ($in_footer) is not set explicitly %s; ' .
				'It is recommended to load scripts in the footer. Please set this value to `true` to load it in the footer, or explicitly `false` if it should be loaded in the header.',
				$stackPtr,
				'NotInFooter',
				array( $matched_content )
			);
		}
	}

	/**
	 * Determine if a range has a falsy value.
	 *
	 * @param int $start The position to start looking from.
	 * @param int $end   The position to stop looking (inclusive).
	 *
	 * @return bool True if the parameter is falsy.
	 *              False if the parameter is not falsy or when it
	 *              couldn't be reliably determined.
	 */
	protected function is_falsy( $start, $end ) {

		// Find anything excluding the false tokens.
		$has_non_false = $this->phpcsFile->findNext( $this->false_tokens, $start, ( $end + 1 ), true );
		// If no non-false tokens are found, we are good.
		if ( false === $has_non_false ) {
			return true;
		}

		$code_string = '';
		for ( $i = $start; $i <= $end; $i++ ) {
			if ( isset( $this->safe_tokens[ $this->tokens[ $i ]['code'] ] ) === false ) {
				// Function call/variable or other token which makes it neigh impossible
				// to determine whether the actual value would evaluate to false.
				return false;
			}

			if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) {
				continue;
			}

			$code_string .= $this->tokens[ $i ]['content'];
		}

		if ( '' === $code_string ) {
			return false;
		}

		// Evaluate the argument to figure out the outcome is false or not.
		// phpcs:ignore Squiz.PHP.Eval -- No harm here.
		return ( false === eval( "return (bool) $code_string;" ) );
	}
}