1<?php 2/** 3 * Ensures that systems, asset types and libs are included before they are used. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer_MySource 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 12 * @link http://pear.php.net/package/PHP_CodeSniffer 13 */ 14 15if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) { 16 $error = 'Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found'; 17 throw new PHP_CodeSniffer_Exception($error); 18} 19 20/** 21 * Ensures that systems, asset types and libs are included before they are used. 22 * 23 * @category PHP 24 * @package PHP_CodeSniffer_MySource 25 * @author Greg Sherwood <gsherwood@squiz.net> 26 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 27 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 28 * @version Release: @package_version@ 29 * @link http://pear.php.net/package/PHP_CodeSniffer 30 */ 31class MySource_Sniffs_Channels_IncludeSystemSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff 32{ 33 34 /** 35 * A list of classes that don't need to be included. 36 * 37 * @var array(string) 38 */ 39 private $_ignore = array( 40 'self' => true, 41 'static' => true, 42 'parent' => true, 43 'channels' => true, 44 'basesystem' => true, 45 'dal' => true, 46 'init' => true, 47 'pdo' => true, 48 'util' => true, 49 'ziparchive' => true, 50 'phpunit_framework_assert' => true, 51 'abstractmysourceunittest' => true, 52 'abstractdatacleanunittest' => true, 53 'exception' => true, 54 'abstractwidgetwidgettype' => true, 55 'domdocument' => true, 56 ); 57 58 59 /** 60 * Constructs a Squiz_Sniffs_Scope_MethodScopeSniff. 61 */ 62 public function __construct() 63 { 64 parent::__construct(array(T_FUNCTION), array(T_DOUBLE_COLON, T_EXTENDS), true); 65 66 }//end __construct() 67 68 69 /** 70 * Processes the function tokens within the class. 71 * 72 * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. 73 * @param integer $stackPtr The position where the token was found. 74 * @param integer $currScope The current scope opener token. 75 * 76 * @return void 77 */ 78 protected function processTokenWithinScope( 79 PHP_CodeSniffer_File $phpcsFile, 80 $stackPtr, 81 $currScope 82 ) { 83 $tokens = $phpcsFile->getTokens(); 84 85 // Determine the name of the class that the static function 86 // is being called on. 87 $classNameToken = $phpcsFile->findPrevious( 88 T_WHITESPACE, 89 ($stackPtr - 1), 90 null, 91 true 92 ); 93 94 // Don't process class names represented by variables as this can be 95 // an inexact science. 96 if ($tokens[$classNameToken]['code'] === T_VARIABLE) { 97 return; 98 } 99 100 $className = $tokens[$classNameToken]['content']; 101 if (isset($this->_ignore[strtolower($className)]) === true) { 102 return; 103 } 104 105 $includedClasses = array(); 106 107 $fileName = strtolower($phpcsFile->getFilename()); 108 $matches = array(); 109 if (preg_match('|/systems/(.*)/([^/]+)?actions.inc$|', $fileName, $matches) !== 0) { 110 // This is an actions file, which means we don't 111 // have to include the system in which it exists. 112 $includedClasses[$matches[2]] = true; 113 114 // Or a system it implements. 115 $class = $phpcsFile->getCondition($stackPtr, T_CLASS); 116 $implements = $phpcsFile->findNext(T_IMPLEMENTS, $class, ($class + 10)); 117 if ($implements !== false) { 118 $implementsClass = $phpcsFile->findNext(T_STRING, $implements); 119 $implementsClassName = strtolower($tokens[$implementsClass]['content']); 120 if (substr($implementsClassName, -7) === 'actions') { 121 $includedClasses[substr($implementsClassName, 0, -7)] = true; 122 } 123 } 124 } 125 126 // Go searching for includeSystem and includeAsset calls within this 127 // function, or the inclusion of .inc files, which 128 // would be library files. 129 for ($i = ($currScope + 1); $i < $stackPtr; $i++) { 130 $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); 131 if ($name !== false) { 132 $includedClasses[$name] = true; 133 // Special case for Widgets cause they are, well, special. 134 } else if (strtolower($tokens[$i]['content']) === 'includewidget') { 135 $typeName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($i + 1)); 136 $typeName = trim($tokens[$typeName]['content'], " '"); 137 $includedClasses[strtolower($typeName).'widgettype'] = true; 138 } 139 } 140 141 // Now go searching for includeSystem, includeAsset or require/include 142 // calls outside our scope. If we are in a class, look outside the 143 // class. If we are not, look outside the function. 144 $condPtr = $currScope; 145 if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { 146 foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { 147 if ($condType === T_CLASS) { 148 break; 149 } 150 } 151 } 152 153 for ($i = 0; $i < $condPtr; $i++) { 154 // Skip other scopes. 155 if (isset($tokens[$i]['scope_closer']) === true) { 156 $i = $tokens[$i]['scope_closer']; 157 continue; 158 } 159 160 $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); 161 if ($name !== false) { 162 $includedClasses[$name] = true; 163 } 164 } 165 166 // If we are in a testing class, we might have also included 167 // some systems and classes in our setUp() method. 168 $setupFunction = null; 169 if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { 170 foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { 171 if ($condType === T_CLASS) { 172 // Is this is a testing class? 173 $name = $phpcsFile->findNext(T_STRING, $condPtr); 174 $name = $tokens[$name]['content']; 175 if (substr($name, -8) === 'UnitTest') { 176 // Look for a method called setUp(). 177 $end = $tokens[$condPtr]['scope_closer']; 178 $function = $phpcsFile->findNext(T_FUNCTION, ($condPtr + 1), $end); 179 while ($function !== false) { 180 $name = $phpcsFile->findNext(T_STRING, $function); 181 if ($tokens[$name]['content'] === 'setUp') { 182 $setupFunction = $function; 183 break; 184 } 185 186 $function = $phpcsFile->findNext(T_FUNCTION, ($function + 1), $end); 187 } 188 } 189 } 190 }//end foreach 191 }//end if 192 193 if ($setupFunction !== null) { 194 $start = ($tokens[$setupFunction]['scope_opener'] + 1); 195 $end = $tokens[$setupFunction]['scope_closer']; 196 for ($i = $start; $i < $end; $i++) { 197 $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); 198 if ($name !== false) { 199 $includedClasses[$name] = true; 200 } 201 } 202 }//end if 203 204 if (isset($includedClasses[strtolower($className)]) === false) { 205 $error = 'Static method called on non-included class or system "%s"; include system with Channels::includeSystem() or include class with require_once'; 206 $data = array($className); 207 $phpcsFile->addError($error, $stackPtr, 'NotIncludedCall', $data); 208 } 209 210 }//end processTokenWithinScope() 211 212 213 /** 214 * Processes a token within the scope that this test is listening to. 215 * 216 * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. 217 * @param int $stackPtr The position in the stack where 218 * this token was found. 219 * 220 * @return void 221 */ 222 protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 223 { 224 $tokens = $phpcsFile->getTokens(); 225 226 if ($tokens[$stackPtr]['code'] === T_EXTENDS) { 227 // Find the class name. 228 $classNameToken = $phpcsFile->findNext(T_STRING, ($stackPtr + 1)); 229 $className = $tokens[$classNameToken]['content']; 230 } else { 231 // Determine the name of the class that the static function 232 // is being called on. But don't process class names represented by 233 // variables as this can be an inexact science. 234 $classNameToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 235 if ($tokens[$classNameToken]['code'] === T_VARIABLE) { 236 return; 237 } 238 239 $className = $tokens[$classNameToken]['content']; 240 } 241 242 // Some systems are always available. 243 if (isset($this->_ignore[strtolower($className)]) === true) { 244 return; 245 } 246 247 $includedClasses = array(); 248 249 $fileName = strtolower($phpcsFile->getFilename()); 250 $matches = array(); 251 if (preg_match('|/systems/([^/]+)/([^/]+)?actions.inc$|', $fileName, $matches) !== 0) { 252 // This is an actions file, which means we don't 253 // have to include the system in which it exists 254 // We know the system from the path. 255 $includedClasses[$matches[1]] = true; 256 } 257 258 // Go searching for includeSystem, includeAsset or require/include 259 // calls outside our scope. 260 for ($i = 0; $i < $stackPtr; $i++) { 261 // Skip classes and functions as will we never get 262 // into their scopes when including this file, although 263 // we have a chance of getting into IF's, WHILE's etc. 264 if (($tokens[$i]['code'] === T_CLASS 265 || $tokens[$i]['code'] === T_INTERFACE 266 || $tokens[$i]['code'] === T_FUNCTION) 267 && isset($tokens[$i]['scope_closer']) === true 268 ) { 269 $i = $tokens[$i]['scope_closer']; 270 continue; 271 } 272 273 $name = $this->getIncludedClassFromToken($phpcsFile, $tokens, $i); 274 if ($name !== false) { 275 $includedClasses[$name] = true; 276 // Special case for Widgets cause they are, well, special. 277 } else if (strtolower($tokens[$i]['content']) === 'includewidget') { 278 $typeName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($i + 1)); 279 $typeName = trim($tokens[$typeName]['content'], " '"); 280 $includedClasses[strtolower($typeName).'widgettype'] = true; 281 } 282 }//end for 283 284 if (isset($includedClasses[strtolower($className)]) === false) { 285 if ($tokens[$stackPtr]['code'] === T_EXTENDS) { 286 $error = 'Class extends non-included class or system "%s"; include system with Channels::includeSystem() or include class with require_once'; 287 $data = array($className); 288 $phpcsFile->addError($error, $stackPtr, 'NotIncludedExtends', $data); 289 } else { 290 $error = 'Static method called on non-included class or system "%s"; include system with Channels::includeSystem() or include class with require_once'; 291 $data = array($className); 292 $phpcsFile->addError($error, $stackPtr, 'NotIncludedCall', $data); 293 } 294 } 295 296 }//end processTokenOutsideScope() 297 298 299 /** 300 * Determines the included class name from given token. 301 * 302 * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. 303 * @param array $tokens The array of file tokens. 304 * @param int $stackPtr The position in the tokens array of the 305 * potentially included class. 306 * 307 * @return string 308 */ 309 protected function getIncludedClassFromToken( 310 PHP_CodeSniffer_File $phpcsFile, 311 array $tokens, 312 $stackPtr 313 ) { 314 if (strtolower($tokens[$stackPtr]['content']) === 'includesystem') { 315 $systemName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($stackPtr + 1)); 316 $systemName = trim($tokens[$systemName]['content'], " '"); 317 return strtolower($systemName); 318 } else if (strtolower($tokens[$stackPtr]['content']) === 'includeasset') { 319 $typeName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($stackPtr + 1)); 320 $typeName = trim($tokens[$typeName]['content'], " '"); 321 return strtolower($typeName).'assettype'; 322 } else if (isset(PHP_CodeSniffer_Tokens::$includeTokens[$tokens[$stackPtr]['code']]) === true) { 323 $filePath = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($stackPtr + 1)); 324 $filePath = $tokens[$filePath]['content']; 325 $filePath = trim($filePath, " '"); 326 $filePath = basename($filePath, '.inc'); 327 return strtolower($filePath); 328 } 329 330 return false; 331 332 }//end getIncludedClassFromToken() 333 334 335}//end class 336