*/ protected $methods = array( 'get_var' => true, 'get_col' => true, 'get_row' => true, 'get_results' => true, 'prepare' => true, 'query' => true, ); /** * Functions that escape values for use in SQL queries. * * @since 0.9.0 * @since 0.11.0 Changed from public static to protected non-static. * @since 3.0.0 - Moved from the Sniff class to this class. * - The property visibility has changed from `protected` to `private`. * * @var array */ private $SQLEscapingFunctions = array( 'absint' => true, 'esc_sql' => true, 'floatval' => true, 'intval' => true, 'like_escape' => true, ); /** * Functions whose output is automatically escaped for use in SQL queries. * * @since 0.9.0 * @since 0.11.0 Changed from public static to protected non-static. * @since 3.0.0 - Moved from the Sniff class to this class. * - The property visibility has changed from `protected` to `private`. * * @var array */ private $SQLAutoEscapedFunctions = array( 'count' => true, ); /** * Tokens that we don't flag when they are found in a $wpdb method call. * * This token array is augmented from within the register() method. * * @since 0.9.0 * @since 3.0.0 The property visibility has changed from `protected` to `private`. * * @var array */ private $ignored_tokens = array( \T_STRING_CONCAT => true, \T_CONSTANT_ENCAPSED_STRING => true, \T_COMMA => true, \T_LNUMBER => true, \T_DNUMBER => true, \T_NS_SEPARATOR => true, ); /** * A loop pointer. * * It is a property so that we can access it in all of our methods. * * @since 0.9.0 * * @var int */ protected $i; /** * The loop end marker. * * It is a property so that we can access it in all of our methods. * * @since 0.9.0 * * @var int */ protected $end; /** * Returns an array of tokens this test wants to listen for. * * @since 0.8.0 * * @return array */ public function register() { // Enrich the array of tokens which can be safely ignored. $this->ignored_tokens += Tokens::$bracketTokens; $this->ignored_tokens += Tokens::$heredocTokens; $this->ignored_tokens += Tokens::$castTokens; $this->ignored_tokens += Tokens::$arithmeticTokens; $this->ignored_tokens += Collections::incrementDecrementOperators(); $this->ignored_tokens += Collections::objectOperators(); $this->ignored_tokens += Tokens::$emptyTokens; // The contents of heredoc tokens needs to be examined. unset( $this->ignored_tokens[ \T_HEREDOC ] ); return array( \T_VARIABLE, \T_STRING, ); } /** * Processes this test, when one of its tokens is encountered. * * @since 0.8.0 * * @param int $stackPtr The position of the current token in the stack. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. */ public function process_token( $stackPtr ) { if ( ! $this->is_wpdb_method_call( $this->phpcsFile, $stackPtr, $this->methods ) ) { return; } for ( $this->i; $this->i < $this->end; $this->i++ ) { if ( isset( $this->ignored_tokens[ $this->tokens[ $this->i ]['code'] ] ) ) { continue; } if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $this->i ]['code'] || \T_HEREDOC === $this->tokens[ $this->i ]['code'] ) { $bad_variables = array_filter( TextStrings::getEmbeds( $this->tokens[ $this->i ]['content'] ), static function ( $symbol ) { return preg_match( '`^\{?\$\{?wpdb\??->`', $symbol ) !== 1; } ); foreach ( $bad_variables as $bad_variable ) { $this->phpcsFile->addError( 'Use placeholders and $wpdb->prepare(); found interpolated variable %s at %s', $this->i, 'InterpolatedNotPrepared', array( $bad_variable, $this->tokens[ $this->i ]['content'], ) ); } continue; } if ( \T_VARIABLE === $this->tokens[ $this->i ]['code'] ) { if ( '$wpdb' === $this->tokens[ $this->i ]['content'] ) { $this->is_wpdb_method_call( $this->phpcsFile, $this->i, $this->methods ); continue; } if ( ContextHelper::is_safe_casted( $this->phpcsFile, $this->i ) ) { continue; } } if ( \T_STRING === $this->tokens[ $this->i ]['code'] ) { if ( isset( $this->SQLEscapingFunctions[ $this->tokens[ $this->i ]['content'] ] ) || isset( $this->SQLAutoEscapedFunctions[ $this->tokens[ $this->i ]['content'] ] ) ) { // Find the opening parenthesis. $opening_paren = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $this->i + 1 ), null, true ); if ( false !== $opening_paren && \T_OPEN_PARENTHESIS === $this->tokens[ $opening_paren ]['code'] && isset( $this->tokens[ $opening_paren ]['parenthesis_closer'] ) ) { // Skip past to the end of the function call. $this->i = $this->tokens[ $opening_paren ]['parenthesis_closer']; continue; } } elseif ( FormattingFunctionsHelper::is_formatting_function( $this->tokens[ $this->i ]['content'] ) ) { continue; } } $this->phpcsFile->addError( 'Use placeholders and $wpdb->prepare(); found %s', $this->i, 'NotPrepared', array( $this->tokens[ $this->i ]['content'] ) ); } return $this->end; } }