/** * 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) { $tokens = $phpcsFile->getTokens(); $function = $phpcsFile->getCondition($stackPtr, T_FUNCTION); if ($function === false) { // Not a nested function. return; } $class = $phpcsFile->getCondition($stackPtr, T_ANON_CLASS); if ($class !== false && $class > $function) { // Ignore methods in anon classes. return; } $prev = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); if ($tokens[$prev]['code'] === T_EQUAL) { // Ignore closures. return; } $error = 'The use of inner functions is forbidden'; $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); }
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (!$phpcsFile->getCondition($stackPtr, T_INTERFACE)) { // this function is not part of an interface return; } $sequence = SequenceBuilder::create()->lookingBackward()->expect()->quantity()->any()->choice()->token(T_WHITESPACE)->token(T_PUBLIC)->token(T_STATIC)->end()->end()->quantity()->atLeast(3)->token(T_DOC_COMMENT)->end()->end()->build(); /** @var $sequence \Matthias\Codesniffer\Sequence\ForwardSequence */ if (!$sequence->matches($tokens, $stackPtr)) { $phpcsFile->addError('Interface method should have a doc comment', $stackPtr); } }
/** * Processes the function tokens within the class. * * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. * @param integer $stackPtr The position where the token was found. * @param integer $currScope The current scope opener token. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $tokens = $phpcsFile->getTokens(); // Determine the name of the class that the static function // is being called on. $classNameToken = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); $className = $tokens[$classNameToken]['content']; if (in_array(strtolower($className), $this->_ignore) === true) { return; } $includedClasses = array(); $fileName = strtolower($phpcsFile->getFilename()); $matches = array(); if (preg_match('|/systems/(.*)/([^/]+)?actions.inc$|', $fileName, $matches) !== 0) { // This is an actions file, which means we don't // have to include the system in which it exists. $includedClasses[] = $matches[2]; // Or a system it implements. $class = $phpcsFile->getCondition($stackPtr, T_CLASS); $implements = $phpcsFile->findNext(T_IMPLEMENTS, $class, $class + 10); if ($implements !== false) { $implementsClass = $phpcsFile->findNext(T_STRING, $implements); $implementsClassName = strtolower($tokens[$implementsClass]['content']); if (substr($implementsClassName, -7) === 'actions') { $includedClasses[] = substr($implementsClassName, 0, -7); } } } // Go searching for includeSystem and includeAsset calls within this // function, or the inclusion of .inc files, which // would be library files. for ($i = $currScope + 1; $i < $stackPtr; $i++) { $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); if ($name !== false) { $includedClasses[] = $name; // Special case for Widgets cause they are, well, special. } else { if (strtolower($tokens[$i]['content']) === 'includewidget') { $typeName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $i + 1); $typeName = trim($tokens[$typeName]['content'], " '"); $includedClasses[] = strtolower($typeName) . 'widgettype'; } } } // Now go searching for includeSystem, includeAsset or require/include // calls outside our scope. If we are in a class, look outside the // class. If we are not, look outside the function. $condPtr = $currScope; if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { if ($condType === T_CLASS) { break; } } } for ($i = 0; $i < $condPtr; $i++) { // Skip other scopes. if (isset($tokens[$i]['scope_closer']) === true) { $i = $tokens[$i]['scope_closer']; continue; } $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); if ($name !== false) { $includedClasses[] = $name; } } //end for // If we are in a testing class, we might have also included // some systems and classes in our setUp() method. $setupFunction = null; if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { if ($condType === T_CLASS) { // Is this is a testing class? $name = $phpcsFile->findNext(T_STRING, $condPtr); $name = $tokens[$name]['content']; if (substr($name, -8) === 'UnitTest') { // Look for a method called setUp(). $end = $tokens[$condPtr]['scope_closer']; $function = $phpcsFile->findNext(T_FUNCTION, $condPtr + 1, $end); while ($function !== false) { $name = $phpcsFile->findNext(T_STRING, $function); if ($tokens[$name]['content'] === 'setUp') { $setupFunction = $function; break; } $function = $phpcsFile->findNext(T_FUNCTION, $function + 1, $end); } } } } //end foreach } //end if if ($setupFunction !== null) { $start = $tokens[$setupFunction]['scope_opener'] + 1; $end = $tokens[$setupFunction]['scope_closer']; for ($i = $start; $i < $end; $i++) { $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); if ($name !== false) { $includedClasses[] = $name; } } } //end if if (in_array(strtolower($className), $includedClasses) === false) { $error = 'Static method called on non-included class or system "%s"; include system with Channels::includeSystem() or include class with require_once'; $data = array($className); $phpcsFile->addError($error, $stackPtr, 'NotIncludedCall', $data); } }
/** * Check if this token has an associated nonce check. * * @since 0.5.0 * * @param int $stackPtr The position of the current token in the stack of tokens. * * @return bool */ protected function has_nonce_check( $stackPtr ) { /** * @var array { * A cache of the scope that we last checked for nonce verification in. * * @var string $file The name of the file. * @var int $start The index of the token where the scope started. * @var int $end The index of the token where the scope ended. * @var bool|int $nonce_check The index of the token where an nonce * check was found, or false if none was found. * } */ static $last; $start = 0; $end = $stackPtr; $tokens = $this->phpcsFile->getTokens(); // If we're in a function, only look inside of it. $f = $this->phpcsFile->getCondition( $stackPtr, T_FUNCTION ); if ( $f ) { $start = $tokens[ $f ]['scope_opener']; } $in_isset = $this->is_in_isset_or_empty( $stackPtr ); // We allow for isset( $_POST['var'] ) checks to come before the nonce check. // If this is inside an isset(), check after it as well, all the way to the // end of the scope. if ( $in_isset ) { $end = ( 0 === $start ) ? count( $tokens ) : $tokens[ $start ]['scope_closer']; } // Check if we've looked here before. $filename = $this->phpcsFile->getFilename(); if ( $filename === $last['file'] && $start === $last['start'] ) { if ( false !== $last['nonce_check'] ) { // If we have already found an nonce check in this scope, we just // need to check whether it comes before this token. It is OK if the // check is after the token though, if this was only a isset() check. return ( $in_isset || $last['nonce_check'] < $stackPtr ); } elseif ( $end <= $last['end'] ) { // If not, we can still go ahead and return false if we've already // checked to the end of the search area. return false; } // We haven't checked this far yet, but we can still save work by // skipping over the part we've already checked. $start = $last['end']; } else { $last = array( 'file' => $filename, 'start' => $start, 'end' => $end, ); } // Loop through the tokens looking for nonce verification functions. for ( $i = $start; $i < $end; $i++ ) { // If this isn't a function name, skip it. if ( T_STRING !== $tokens[ $i ]['code'] ) { continue; } // If this is one of the nonce verification functions, we can bail out. if ( isset( self::$nonceVerificationFunctions[ $tokens[ $i ]['content'] ] ) ) { $last['nonce_check'] = $i; return true; } } // We're still here, so no luck. $last['nonce_check'] = false; return false; }
/** * 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; }