/**
  * Processes this test, when one of its tokens is encountered.
  *
  * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
  * @param int                  $stackPtr  The position of the current token
  *                                        in the stack passed in $tokens.
  *
  * @return void
  */
 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
 {
     // Merge any custom functions with the defaults, if we haven't already.
     if (!self::$addedCustomFunctions) {
         WordPress_Sniff::$sanitizingFunctions = array_merge(WordPress_Sniff::$sanitizingFunctions, array_flip($this->customSanitizingFunctions));
         WordPress_Sniff::$unslashingSanitizingFunctions = array_merge(WordPress_Sniff::$unslashingSanitizingFunctions, array_flip($this->customUnslashingSanitizingFunctions));
         self::$addedCustomFunctions = true;
     }
     $this->init($phpcsFile);
     $tokens = $phpcsFile->getTokens();
     $superglobals = WordPress_Sniff::$input_superglobals;
     // Handling string interpolation
     if (T_DOUBLE_QUOTED_STRING === $tokens[$stackPtr]['code']) {
         foreach ($superglobals as $superglobal) {
             if (false !== strpos($tokens[$stackPtr]['content'], $superglobal)) {
                 $phpcsFile->addError('Detected usage of a non-sanitized, non-validated input variable: %s', $stackPtr, null, array($tokens[$stackPtr]['content']));
                 return;
             }
         }
         return;
     }
     // Check if this is a superglobal.
     if (!in_array($tokens[$stackPtr]['content'], $superglobals)) {
         return;
     }
     // If we're overriding a superglobal with an assignment, no need to test
     if ($this->is_assignment($stackPtr)) {
         return;
     }
     // This superglobal is being validated.
     if ($this->is_in_isset_or_empty($stackPtr)) {
         return;
     }
     $array_key = $this->get_array_access_key($stackPtr);
     if (empty($array_key)) {
         return;
     }
     // Check for validation first.
     if (!$this->is_validated($stackPtr, $array_key, $this->check_validation_in_scope_only)) {
         $phpcsFile->addError('Detected usage of a non-validated input variable: %s', $stackPtr, 'InputNotValidated', array($tokens[$stackPtr]['content']));
         // return; // Should we just return and not look for sanitizing functions ?
     }
     if ($this->has_whitelist_comment('sanitization', $stackPtr)) {
         return;
     }
     // If this is a comparison ('a' == $_POST['foo']), sanitization isn't needed.
     if ($this->is_comparison($stackPtr)) {
         return;
     }
     // Now look for sanitizing functions
     if (!$this->is_sanitized($stackPtr, true)) {
         $phpcsFile->addError('Detected usage of a non-sanitized input variable: %s', $stackPtr, 'InputNotSanitized', array($tokens[$stackPtr]['content']));
     }
     return;
 }
 /**
  * Processes this test, when one of its tokens is encountered.
  *
  * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
  * @param int                  $stackPtr  The position of the current token
  *                                        in the stack passed in $tokens.
  *
  * @return int|void
  */
 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
 {
     // Merge any custom functions with the defaults, if we haven't already.
     if (!self::$addedCustomFunctions) {
         WordPress_Sniff::$escapingFunctions = array_merge(WordPress_Sniff::$escapingFunctions, array_flip($this->customEscapingFunctions));
         WordPress_Sniff::$autoEscapedFunctions = array_merge(WordPress_Sniff::$autoEscapedFunctions, array_flip($this->customAutoEscapedFunctions));
         WordPress_Sniff::$printingFunctions = array_merge(WordPress_Sniff::$printingFunctions, array_flip($this->customPrintingFunctions));
         if (!empty($this->customSanitizingFunctions)) {
             WordPress_Sniff::$escapingFunctions = array_merge(WordPress_Sniff::$escapingFunctions, array_flip($this->customSanitizingFunctions));
             $phpcsFile->addWarning('The customSanitizingFunctions property is deprecated in favor of customEscapingFunctions.', 0, 'DeprecatedCustomSanitizingFunctions');
         }
         self::$addedCustomFunctions = true;
     }
     $this->init($phpcsFile);
     $tokens = $phpcsFile->getTokens();
     $function = $tokens[$stackPtr]['content'];
     // Find the opening parenthesis (if present; T_ECHO might not have it).
     $open_paren = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true);
     // If function, not T_ECHO nor T_PRINT
     if ($tokens[$stackPtr]['code'] == T_STRING) {
         // Skip if it is a function but is not of the printing functions.
         if (!isset(self::$printingFunctions[$tokens[$stackPtr]['content']])) {
             return;
         }
         if (isset($tokens[$open_paren]['parenthesis_closer'])) {
             $end_of_statement = $tokens[$open_paren]['parenthesis_closer'];
         }
         // These functions only need to have the first argument escaped.
         if (in_array($function, array('trigger_error', 'user_error'))) {
             $end_of_statement = $phpcsFile->findEndOfStatement($open_paren + 1);
         }
     }
     // Checking for the ignore comment, ex: //xss ok
     if ($this->has_whitelist_comment('xss', $stackPtr)) {
         return;
     }
     if (isset($end_of_statement, self::$unsafePrintingFunctions[$function])) {
         $error = $phpcsFile->addError("Expected next thing to be an escaping function (like %s), not '%s'", $stackPtr, 'UnsafePrintingFunction', array(self::$unsafePrintingFunctions[$function], $function));
         // If the error was reported, don't bother checking the function's arguments.
         if ($error) {
             return $end_of_statement;
         }
     }
     $ternary = false;
     // This is already determined if this is a function and not T_ECHO.
     if (!isset($end_of_statement)) {
         $end_of_statement = $phpcsFile->findNext(array(T_SEMICOLON, T_CLOSE_TAG), $stackPtr);
         $last_token = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $end_of_statement - 1, null, true);
         // Check for the ternary operator. We only need to do this here if this
         // echo is lacking parenthesis. Otherwise it will be handled below.
         if (T_OPEN_PARENTHESIS !== $tokens[$open_paren]['code'] || T_CLOSE_PARENTHESIS !== $tokens[$last_token]['code']) {
             $ternary = $phpcsFile->findNext(T_INLINE_THEN, $stackPtr, $end_of_statement);
             // If there is a ternary skip over the part before the ?. However, if
             // the ternary is within parentheses, it will be handled in the loop.
             if ($ternary && empty($tokens[$ternary]['nested_parenthesis'])) {
                 $stackPtr = $ternary;
             }
         }
     }
     // Ignore the function itself.
     $stackPtr++;
     $in_cast = false;
     // looping through echo'd components
     $watch = true;
     for ($i = $stackPtr; $i < $end_of_statement; $i++) {
         // Ignore whitespaces and comments.
         if (in_array($tokens[$i]['code'], array(T_WHITESPACE, T_COMMENT))) {
             continue;
         }
         if (T_OPEN_PARENTHESIS === $tokens[$i]['code']) {
             if ($in_cast) {
                 // Skip to the end of a function call if it has been casted to a safe value.
                 $i = $tokens[$i]['parenthesis_closer'];
                 $in_cast = false;
             } else {
                 // Skip over the condition part of a ternary (i.e., to after the ?).
                 $ternary = $phpcsFile->findNext(T_INLINE_THEN, $i, $tokens[$i]['parenthesis_closer']);
                 if ($ternary) {
                     $next_paren = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $i, $tokens[$i]['parenthesis_closer']);
                     // We only do it if the ternary isn't within a subset of parentheses.
                     if (!$next_paren || $ternary > $tokens[$next_paren]['parenthesis_closer']) {
                         $i = $ternary;
                     }
                 }
             }
             continue;
         }
         // Handle arrays for those functions that accept them.
         if ($tokens[$i]['code'] === T_ARRAY) {
             $i++;
             // Skip the opening parenthesis.
             continue;
         }
         if (in_array($tokens[$i]['code'], array(T_DOUBLE_ARROW, T_CLOSE_PARENTHESIS))) {
             continue;
         }
         // Handle magic constants for debug functions.
         if (in_array($tokens[$i]['code'], array(T_METHOD_C, T_FUNC_C, T_FILE, T_CLASS_C))) {
             continue;
         }
         // Wake up on concatenation characters, another part to check
         if (in_array($tokens[$i]['code'], array(T_STRING_CONCAT))) {
             $watch = true;
             continue;
         }
         // Wake up after a ternary else (:).
         if ($ternary && in_array($tokens[$i]['code'], array(T_INLINE_ELSE))) {
             $watch = true;
             continue;
         }
         // Wake up for commas.
         if ($tokens[$i]['code'] === T_COMMA) {
             $in_cast = false;
             $watch = true;
             continue;
         }
         if ($watch === false) {
             continue;
         }
         // Allow T_CONSTANT_ENCAPSED_STRING eg: echo 'Some String';
         // Also T_LNUMBER, e.g.: echo 45; exit -1;
         if (in_array($tokens[$i]['code'], array(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_MINUS))) {
             continue;
         }
         $watch = false;
         // Allow int/double/bool casted variables
         if (in_array($tokens[$i]['code'], array(T_INT_CAST, T_DOUBLE_CAST, T_BOOL_CAST))) {
             $in_cast = true;
             continue;
         }
         // Now check that next token is a function call.
         if (T_STRING === $this->tokens[$i]['code']) {
             $functionName = $this->tokens[$i]['content'];
             $is_formatting_function = isset(self::$formattingFunctions[$functionName]);
             // Skip pointer to after the function.
             if ($_pos = $this->phpcsFile->findNext(array(T_OPEN_PARENTHESIS), $i, null, null, null, true)) {
                 // If this is a formatting function we just skip over the opening
                 // parenthesis. Otherwise we skip all the way to the closing.
                 if ($is_formatting_function) {
                     $i = $_pos + 1;
                     $watch = true;
                 } else {
                     $i = $this->tokens[$_pos]['parenthesis_closer'];
                 }
             }
             // If this is a safe function, we don't flag it.
             if ($is_formatting_function || isset(self::$autoEscapedFunctions[$functionName]) || isset(self::$escapingFunctions[$functionName])) {
                 continue;
             }
         }
         $this->phpcsFile->addError("Expected next thing to be an escaping function (see Codex for 'Data Validation'), not '%s'", $i, 'OutputNotEscaped', $this->tokens[$i]['content']);
     }
     return $end_of_statement;
 }
 /**
  * Processes this test, when one of its tokens is encountered.
  *
  * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
  * @param int                  $stackPtr  The position of the current token
  *                                        in the stack passed in $tokens.
  *
  * @return int|void
  */
 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
 {
     if (!isset(self::$methods['all'])) {
         self::$methods['all'] = array_merge(self::$methods['cachable'], self::$methods['noncachable']);
         WordPress_Sniff::$cacheGetFunctions = array_merge(WordPress_Sniff::$cacheGetFunctions, array_flip($this->customCacheGetFunctions));
         WordPress_Sniff::$cacheSetFunctions = array_merge(WordPress_Sniff::$cacheSetFunctions, array_flip($this->customCacheSetFunctions));
         WordPress_Sniff::$cacheDeleteFunctions = array_merge(WordPress_Sniff::$cacheDeleteFunctions, array_flip($this->customCacheDeleteFunctions));
     }
     $tokens = $phpcsFile->getTokens();
     // Check for $wpdb variable
     if ($tokens[$stackPtr]['content'] != '$wpdb') {
         return;
     }
     $is_object_call = $phpcsFile->findNext(array(T_OBJECT_OPERATOR), $stackPtr + 1, null, null, null, true);
     if (false == $is_object_call) {
         return;
     }
     // This is not a call to the wpdb object
     $methodPtr = $phpcsFile->findNext(array(T_WHITESPACE), $is_object_call + 1, null, true, null, true);
     $method = $tokens[$methodPtr]['content'];
     if (!isset(self::$methods['all'][$method])) {
         return;
     }
     $endOfStatement = $phpcsFile->findNext(array(T_SEMICOLON), $stackPtr + 1, null, null, null, true);
     $endOfLineComment = '';
     for ($i = $endOfStatement + 1; $i < count($tokens); $i++) {
         if ($tokens[$i]['line'] !== $tokens[$endOfStatement]['line']) {
             break;
         }
         if ($tokens[$i]['code'] === T_COMMENT) {
             $endOfLineComment .= $tokens[$i]['content'];
         }
     }
     $whitelisted_db_call = false;
     if (preg_match('/db call\\W*(ok|pass|clear|whitelist)/i', $endOfLineComment, $matches)) {
         $whitelisted_db_call = true;
     }
     // Check for Database Schema Changes
     $_pos = $stackPtr;
     while ($_pos = $phpcsFile->findNext(array(T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_QUOTED_STRING), $_pos + 1, $endOfStatement, null, null, true)) {
         if (preg_match('#\\b(ALTER|CREATE|DROP)\\b#i', $tokens[$_pos]['content'], $matches) > 0) {
             $phpcsFile->addError('Attempting a database schema change is highly discouraged.', $_pos, 'SchemaChange');
         }
     }
     // Flag instance if not whitelisted
     if (!$whitelisted_db_call) {
         $phpcsFile->addWarning('Usage of a direct database call is discouraged.', $stackPtr, 'DirectQuery');
     }
     if (!isset(self::$methods['cachable'][$method])) {
         return $endOfStatement;
     }
     $whitelisted_cache = false;
     $cached = $wp_cache_get = false;
     if (preg_match('/cache\\s+(ok|pass|clear|whitelist)/i', $endOfLineComment, $matches)) {
         $whitelisted_cache = true;
     }
     if (!$whitelisted_cache && !empty($tokens[$stackPtr]['conditions'])) {
         $scope_function = $phpcsFile->getCondition($stackPtr, T_FUNCTION);
         if ($scope_function) {
             $scopeStart = $tokens[$scope_function]['scope_opener'];
             $scopeEnd = $tokens[$scope_function]['scope_closer'];
             for ($i = $scopeStart + 1; $i < $scopeEnd; $i++) {
                 if (T_STRING === $tokens[$i]['code']) {
                     if (isset(WordPress_Sniff::$cacheDeleteFunctions[$tokens[$i]['content']])) {
                         if (in_array($method, array('query', 'update', 'replace', 'delete'))) {
                             $cached = true;
                             break;
                         }
                     } elseif (isset(WordPress_Sniff::$cacheGetFunctions[$tokens[$i]['content']])) {
                         $wp_cache_get = true;
                     } elseif (isset(WordPress_Sniff::$cacheSetFunctions[$tokens[$i]['content']])) {
                         if ($wp_cache_get) {
                             $cached = true;
                             break;
                         }
                     }
                 }
             }
         }
     }
     if (!$cached && !$whitelisted_cache) {
         $message = 'Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set or wp_cache_delete.';
         $phpcsFile->addError($message, $stackPtr, 'NoCaching');
     }
     return $endOfStatement;
 }