1<?php 2/** 3 * PHP_CodeSniffer tokenizes PHP code and detects violations of a 4 * defined set of coding standards. 5 * 6 * PHP version 5 7 * 8 * @category PHP 9 * @package PHP_CodeSniffer 10 * @author Greg Sherwood <gsherwood@squiz.net> 11 * @author Marc McIntyre <mmcintyre@squiz.net> 12 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 13 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 14 * @link http://pear.php.net/package/PHP_CodeSniffer 15 */ 16 17spl_autoload_register(array('PHP_CodeSniffer', 'autoload')); 18 19if (class_exists('PHP_CodeSniffer_Exception', true) === false) { 20 throw new Exception('Class PHP_CodeSniffer_Exception not found'); 21} 22 23if (class_exists('PHP_CodeSniffer_File', true) === false) { 24 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found'); 25} 26 27if (class_exists('PHP_CodeSniffer_Fixer', true) === false) { 28 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Fixer not found'); 29} 30 31if (class_exists('PHP_CodeSniffer_Tokens', true) === false) { 32 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found'); 33} 34 35if (class_exists('PHP_CodeSniffer_CLI', true) === false) { 36 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found'); 37} 38 39if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) { 40 throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found'); 41} 42 43/** 44 * PHP_CodeSniffer tokenizes PHP code and detects violations of a 45 * defined set of coding standards. 46 * 47 * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff 48 * interface. A sniff registers what token types it wishes to listen for, then 49 * PHP_CodeSniffer encounters that token, the sniff is invoked and passed 50 * information about where the token was found in the stack, and the token stack 51 * itself. 52 * 53 * Sniff files and their containing class must be prefixed with Sniff, and 54 * have an extension of .php. 55 * 56 * Multiple PHP_CodeSniffer operations can be performed by re-calling the 57 * process function with different parameters. 58 * 59 * @category PHP 60 * @package PHP_CodeSniffer 61 * @author Greg Sherwood <gsherwood@squiz.net> 62 * @author Marc McIntyre <mmcintyre@squiz.net> 63 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 64 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 65 * @version Release: @package_version@ 66 * @link http://pear.php.net/package/PHP_CodeSniffer 67 */ 68class PHP_CodeSniffer 69{ 70 71 /** 72 * The current version. 73 * 74 * @var string 75 */ 76 const VERSION = '2.9.0'; 77 78 /** 79 * Package stability; either stable, beta or alpha. 80 * 81 * @var string 82 */ 83 const STABILITY = 'stable'; 84 85 /** 86 * The file or directory that is currently being processed. 87 * 88 * @var string 89 */ 90 protected $file = ''; 91 92 /** 93 * The directories that the processed rulesets are in. 94 * 95 * This is declared static because it is also used in the 96 * autoloader to look for sniffs outside the PHPCS install. 97 * This way, standards designed to be installed inside PHPCS can 98 * also be used from outside the PHPCS Standards directory. 99 * 100 * @var string 101 */ 102 protected static $rulesetDirs = array(); 103 104 /** 105 * The CLI object controlling the run. 106 * 107 * @var PHP_CodeSniffer_CLI 108 */ 109 public $cli = null; 110 111 /** 112 * The Reporting object controlling report generation. 113 * 114 * @var PHP_CodeSniffer_Reporting 115 */ 116 public $reporting = null; 117 118 /** 119 * An array of sniff objects that are being used to check files. 120 * 121 * @var array(PHP_CodeSniffer_Sniff) 122 */ 123 protected $listeners = array(); 124 125 /** 126 * An array of sniffs that are being used to check files. 127 * 128 * @var array(string) 129 */ 130 protected $sniffs = array(); 131 132 /** 133 * A mapping of sniff codes to fully qualified class names. 134 * 135 * The key is the sniff code and the value 136 * is the fully qualified name of the sniff class. 137 * 138 * @var array<string, string> 139 */ 140 public $sniffCodes = array(); 141 142 /** 143 * The listeners array, indexed by token type. 144 * 145 * @var array 146 */ 147 private $_tokenListeners = array(); 148 149 /** 150 * An array of rules from the ruleset.xml file. 151 * 152 * It may be empty, indicating that the ruleset does not override 153 * any of the default sniff settings. 154 * 155 * @var array 156 */ 157 protected $ruleset = array(); 158 159 /** 160 * An array of patterns to use for skipping files. 161 * 162 * @var array 163 */ 164 protected $ignorePatterns = array(); 165 166 /** 167 * An array of extensions for files we will check. 168 * 169 * @var array 170 */ 171 public $allowedFileExtensions = array(); 172 173 /** 174 * An array of default extensions and associated tokenizers. 175 * 176 * If no extensions are set, these will be used as the defaults. 177 * If extensions are set, these will be used when the correct tokenizer 178 * can not be determined, such as when checking a passed filename instead 179 * of files in a directory. 180 * 181 * @var array 182 */ 183 public $defaultFileExtensions = array( 184 'php' => 'PHP', 185 'inc' => 'PHP', 186 'js' => 'JS', 187 'css' => 'CSS', 188 ); 189 190 /** 191 * An array of variable types for param/var we will check. 192 * 193 * @var array(string) 194 */ 195 public static $allowedTypes = array( 196 'array', 197 'boolean', 198 'float', 199 'integer', 200 'mixed', 201 'object', 202 'string', 203 'resource', 204 'callable', 205 ); 206 207 208 /** 209 * Constructs a PHP_CodeSniffer object. 210 * 211 * @param int $verbosity The verbosity level. 212 * 1: Print progress information. 213 * 2: Print tokenizer debug information. 214 * 3: Print sniff debug information. 215 * @param int $tabWidth The number of spaces each tab represents. 216 * If greater than zero, tabs will be replaced 217 * by spaces before testing each file. 218 * @param string $encoding The charset of the sniffed files. 219 * This is important for some reports that output 220 * with utf-8 encoding as you don't want it double 221 * encoding messages. 222 * @param bool $interactive If TRUE, will stop after each file with errors 223 * and wait for user input. 224 * 225 * @see process() 226 */ 227 public function __construct( 228 $verbosity=0, 229 $tabWidth=0, 230 $encoding='iso-8859-1', 231 $interactive=false 232 ) { 233 if ($verbosity !== null) { 234 $this->setVerbosity($verbosity); 235 } 236 237 if ($tabWidth !== null) { 238 $this->setTabWidth($tabWidth); 239 } 240 241 if ($encoding !== null) { 242 $this->setEncoding($encoding); 243 } 244 245 if ($interactive !== null) { 246 $this->setInteractive($interactive); 247 } 248 249 if (defined('PHPCS_DEFAULT_ERROR_SEV') === false) { 250 define('PHPCS_DEFAULT_ERROR_SEV', 5); 251 } 252 253 if (defined('PHPCS_DEFAULT_WARN_SEV') === false) { 254 define('PHPCS_DEFAULT_WARN_SEV', 5); 255 } 256 257 if (defined('PHP_CODESNIFFER_CBF') === false) { 258 define('PHP_CODESNIFFER_CBF', false); 259 } 260 261 // Set default CLI object in case someone is running us 262 // without using the command line script. 263 $this->cli = new PHP_CodeSniffer_CLI(); 264 $this->cli->errorSeverity = PHPCS_DEFAULT_ERROR_SEV; 265 $this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV; 266 $this->cli->dieOnUnknownArg = false; 267 268 $this->reporting = new PHP_CodeSniffer_Reporting(); 269 270 }//end __construct() 271 272 273 /** 274 * Autoload static method for loading classes and interfaces. 275 * 276 * @param string $className The name of the class or interface. 277 * 278 * @return void 279 */ 280 public static function autoload($className) 281 { 282 if (substr($className, 0, 4) === 'PHP_') { 283 $newClassName = substr($className, 4); 284 } else { 285 $newClassName = $className; 286 } 287 288 $path = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $newClassName).'.php'; 289 290 if (is_file(dirname(__FILE__).DIRECTORY_SEPARATOR.$path) === true) { 291 // Check standard file locations based on class name. 292 include dirname(__FILE__).DIRECTORY_SEPARATOR.$path; 293 return; 294 } else { 295 // Check for included sniffs. 296 $installedPaths = PHP_CodeSniffer::getInstalledStandardPaths(); 297 foreach ($installedPaths as $installedPath) { 298 if (is_file($installedPath.DIRECTORY_SEPARATOR.$path) === true) { 299 include $installedPath.DIRECTORY_SEPARATOR.$path; 300 return; 301 } 302 } 303 304 // Check standard file locations based on the loaded rulesets. 305 foreach (self::$rulesetDirs as $rulesetDir) { 306 if (is_file(dirname($rulesetDir).DIRECTORY_SEPARATOR.$path) === true) { 307 include_once dirname($rulesetDir).DIRECTORY_SEPARATOR.$path; 308 return; 309 } 310 } 311 }//end if 312 313 // Everything else. 314 @include $path; 315 316 }//end autoload() 317 318 319 /** 320 * Sets the verbosity level. 321 * 322 * @param int $verbosity The verbosity level. 323 * 1: Print progress information. 324 * 2: Print tokenizer debug information. 325 * 3: Print sniff debug information. 326 * 327 * @return void 328 */ 329 public function setVerbosity($verbosity) 330 { 331 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) { 332 define('PHP_CODESNIFFER_VERBOSITY', $verbosity); 333 } 334 335 }//end setVerbosity() 336 337 338 /** 339 * Sets the tab width. 340 * 341 * @param int $tabWidth The number of spaces each tab represents. 342 * If greater than zero, tabs will be replaced 343 * by spaces before testing each file. 344 * 345 * @return void 346 */ 347 public function setTabWidth($tabWidth) 348 { 349 if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) { 350 define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth); 351 } 352 353 }//end setTabWidth() 354 355 356 /** 357 * Sets the encoding. 358 * 359 * @param string $encoding The charset of the sniffed files. 360 * This is important for some reports that output 361 * with utf-8 encoding as you don't want it double 362 * encoding messages. 363 * 364 * @return void 365 */ 366 public function setEncoding($encoding) 367 { 368 if (defined('PHP_CODESNIFFER_ENCODING') === false) { 369 define('PHP_CODESNIFFER_ENCODING', $encoding); 370 } 371 372 }//end setEncoding() 373 374 375 /** 376 * Sets the interactive flag. 377 * 378 * @param bool $interactive If TRUE, will stop after each file with errors 379 * and wait for user input. 380 * 381 * @return void 382 */ 383 public function setInteractive($interactive) 384 { 385 if (defined('PHP_CODESNIFFER_INTERACTIVE') === false) { 386 define('PHP_CODESNIFFER_INTERACTIVE', $interactive); 387 } 388 389 }//end setInteractive() 390 391 392 /** 393 * Sets an array of file extensions that we will allow checking of. 394 * 395 * If the extension is one of the defaults, a specific tokenizer 396 * will be used. Otherwise, the PHP tokenizer will be used for 397 * all extensions passed. 398 * 399 * @param array $extensions An array of file extensions. 400 * 401 * @return void 402 */ 403 public function setAllowedFileExtensions(array $extensions) 404 { 405 $newExtensions = array(); 406 foreach ($extensions as $ext) { 407 $slash = strpos($ext, '/'); 408 if ($slash !== false) { 409 // They specified the tokenizer too. 410 list($ext, $tokenizer) = explode('/', $ext); 411 $newExtensions[$ext] = strtoupper($tokenizer); 412 continue; 413 } 414 415 if (isset($this->allowedFileExtensions[$ext]) === true) { 416 $newExtensions[$ext] = $this->allowedFileExtensions[$ext]; 417 } else if (isset($this->defaultFileExtensions[$ext]) === true) { 418 $newExtensions[$ext] = $this->defaultFileExtensions[$ext]; 419 } else { 420 $newExtensions[$ext] = 'PHP'; 421 } 422 } 423 424 $this->allowedFileExtensions = $newExtensions; 425 426 }//end setAllowedFileExtensions() 427 428 429 /** 430 * Sets an array of ignore patterns that we use to skip files and folders. 431 * 432 * Patterns are not case sensitive. 433 * 434 * @param array $patterns An array of ignore patterns. The pattern is the key 435 * and the value is either "absolute" or "relative", 436 * depending on how the pattern should be applied to a 437 * file path. 438 * 439 * @return void 440 */ 441 public function setIgnorePatterns(array $patterns) 442 { 443 $this->ignorePatterns = $patterns; 444 445 }//end setIgnorePatterns() 446 447 448 /** 449 * Gets the array of ignore patterns. 450 * 451 * Optionally takes a listener to get ignore patterns specified 452 * for that sniff only. 453 * 454 * @param string $listener The listener to get patterns for. If NULL, all 455 * patterns are returned. 456 * 457 * @return array 458 */ 459 public function getIgnorePatterns($listener=null) 460 { 461 if ($listener === null) { 462 return $this->ignorePatterns; 463 } 464 465 if (isset($this->ignorePatterns[$listener]) === true) { 466 return $this->ignorePatterns[$listener]; 467 } 468 469 return array(); 470 471 }//end getIgnorePatterns() 472 473 474 /** 475 * Sets the internal CLI object. 476 * 477 * @param object $cli The CLI object controlling the run. 478 * 479 * @return void 480 */ 481 public function setCli($cli) 482 { 483 $this->cli = $cli; 484 485 }//end setCli() 486 487 488 /** 489 * Start a PHP_CodeSniffer run. 490 * 491 * @param string|array $files The files and directories to process. For 492 * directories, each sub directory will also 493 * be traversed for source files. 494 * @param string|array $standards The set of code sniffs we are testing 495 * against. 496 * @param array $restrictions The sniff codes to restrict the 497 * violations to. 498 * @param boolean $local If true, don't recurse into directories. 499 * 500 * @return void 501 */ 502 public function process($files, $standards, array $restrictions=array(), $local=false) 503 { 504 $files = (array) $files; 505 $this->initStandard($standards, $restrictions); 506 $this->processFiles($files, $local); 507 508 }//end process() 509 510 511 /** 512 * Initialise the standard that the run will use. 513 * 514 * @param string|array $standards The set of code sniffs we are testing 515 * against. 516 * @param array $restrictions The sniff codes to restrict the testing to. 517 * @param array $exclusions The sniff codes to exclude from testing. 518 * 519 * @return void 520 */ 521 public function initStandard($standards, array $restrictions=array(), array $exclusions=array()) 522 { 523 $standards = (array) $standards; 524 525 // Reset the members. 526 $this->listeners = array(); 527 $this->sniffs = array(); 528 $this->ruleset = array(); 529 $this->_tokenListeners = array(); 530 self::$rulesetDirs = array(); 531 532 // Ensure this option is enabled or else line endings will not always 533 // be detected properly for files created on a Mac with the /r line ending. 534 ini_set('auto_detect_line_endings', true); 535 536 if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) { 537 // Should be one standard and one sniff being tested at a time. 538 $installed = $this->getInstalledStandardPath($standards[0]); 539 if ($installed !== null) { 540 $standard = $installed; 541 } else { 542 $standard = self::realpath($standards[0]); 543 if (is_dir($standard) === true 544 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true 545 ) { 546 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); 547 } 548 } 549 550 $sniffs = $this->_expandRulesetReference($restrictions[0], dirname($standard)); 551 } else { 552 $sniffs = array(); 553 foreach ($standards as $standard) { 554 $installed = $this->getInstalledStandardPath($standard); 555 if ($installed !== null) { 556 $standard = $installed; 557 } else { 558 $standard = self::realpath($standard); 559 if (is_dir($standard) === true 560 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true 561 ) { 562 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); 563 } 564 } 565 566 if (PHP_CODESNIFFER_VERBOSITY === 1) { 567 $ruleset = simplexml_load_string(file_get_contents($standard)); 568 if ($ruleset !== false) { 569 $standardName = (string) $ruleset['name']; 570 } 571 572 echo "Registering sniffs in the $standardName standard... "; 573 if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) { 574 echo PHP_EOL; 575 } 576 } 577 578 $sniffs = array_merge($sniffs, $this->processRuleset($standard)); 579 }//end foreach 580 }//end if 581 582 $sniffRestrictions = array(); 583 foreach ($restrictions as $sniffCode) { 584 $parts = explode('.', strtolower($sniffCode)); 585 $sniffRestrictions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff'; 586 } 587 588 $sniffExclusions = array(); 589 foreach ($exclusions as $sniffCode) { 590 $parts = explode('.', strtolower($sniffCode)); 591 $sniffExclusions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff'; 592 } 593 594 $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions); 595 $this->populateTokenListeners(); 596 597 if (PHP_CODESNIFFER_VERBOSITY === 1) { 598 $numSniffs = count($this->sniffs); 599 echo "DONE ($numSniffs sniffs registered)".PHP_EOL; 600 } 601 602 }//end initStandard() 603 604 605 /** 606 * Processes the files/directories that PHP_CodeSniffer was constructed with. 607 * 608 * @param string|array $files The files and directories to process. For 609 * directories, each sub directory will also 610 * be traversed for source files. 611 * @param boolean $local If true, don't recurse into directories. 612 * 613 * @return void 614 * @throws PHP_CodeSniffer_Exception If files are invalid. 615 */ 616 public function processFiles($files, $local=false) 617 { 618 $files = (array) $files; 619 $cliValues = $this->cli->getCommandLineValues(); 620 $showProgress = $cliValues['showProgress']; 621 $useColors = $cliValues['colors']; 622 623 if (PHP_CODESNIFFER_VERBOSITY > 0) { 624 echo 'Creating file list... '; 625 } 626 627 if (empty($this->allowedFileExtensions) === true) { 628 $this->allowedFileExtensions = $this->defaultFileExtensions; 629 } 630 631 $todo = $this->getFilesToProcess($files, $local); 632 $numFiles = count($todo); 633 634 if (PHP_CODESNIFFER_VERBOSITY > 0) { 635 echo "DONE ($numFiles files in queue)".PHP_EOL; 636 } 637 638 $numProcessed = 0; 639 $dots = 0; 640 $maxLength = strlen($numFiles); 641 $lastDir = ''; 642 foreach ($todo as $file) { 643 $this->file = $file; 644 $currDir = dirname($file); 645 if ($lastDir !== $currDir) { 646 if (PHP_CODESNIFFER_VERBOSITY > 0 || PHP_CODESNIFFER_CBF === true) { 647 echo 'Changing into directory '.$currDir.PHP_EOL; 648 } 649 650 $lastDir = $currDir; 651 } 652 653 $phpcsFile = $this->processFile($file, null); 654 $numProcessed++; 655 656 if (PHP_CODESNIFFER_VERBOSITY > 0 657 || PHP_CODESNIFFER_INTERACTIVE === true 658 || $showProgress === false 659 ) { 660 continue; 661 } 662 663 // Show progress information. 664 if ($phpcsFile === null) { 665 echo 'S'; 666 } else { 667 $errors = $phpcsFile->getErrorCount(); 668 $warnings = $phpcsFile->getWarningCount(); 669 if ($errors > 0) { 670 if ($useColors === true) { 671 echo "\033[31m"; 672 } 673 674 echo 'E'; 675 } else if ($warnings > 0) { 676 if ($useColors === true) { 677 echo "\033[33m"; 678 } 679 680 echo 'W'; 681 } else { 682 echo '.'; 683 } 684 685 if ($useColors === true) { 686 echo "\033[0m"; 687 } 688 }//end if 689 690 $dots++; 691 if ($dots === 60) { 692 $padding = ($maxLength - strlen($numProcessed)); 693 echo str_repeat(' ', $padding); 694 $percent = round(($numProcessed / $numFiles) * 100); 695 echo " $numProcessed / $numFiles ($percent%)".PHP_EOL; 696 $dots = 0; 697 } 698 }//end foreach 699 700 if (PHP_CODESNIFFER_VERBOSITY === 0 701 && PHP_CODESNIFFER_INTERACTIVE === false 702 && $showProgress === true 703 ) { 704 echo PHP_EOL.PHP_EOL; 705 } 706 707 }//end processFiles() 708 709 710 /** 711 * Processes a single ruleset and returns a list of the sniffs it represents. 712 * 713 * Rules founds within the ruleset are processed immediately, but sniff classes 714 * are not registered by this method. 715 * 716 * @param string $rulesetPath The path to a ruleset XML file. 717 * @param int $depth How many nested processing steps we are in. This 718 * is only used for debug output. 719 * 720 * @return array 721 * @throws PHP_CodeSniffer_Exception If the ruleset path is invalid. 722 */ 723 public function processRuleset($rulesetPath, $depth=0) 724 { 725 $rulesetPath = self::realpath($rulesetPath); 726 if (PHP_CODESNIFFER_VERBOSITY > 1) { 727 echo str_repeat("\t", $depth); 728 echo "Processing ruleset $rulesetPath".PHP_EOL; 729 } 730 731 $ruleset = simplexml_load_string(file_get_contents($rulesetPath)); 732 if ($ruleset === false) { 733 throw new PHP_CodeSniffer_Exception("Ruleset $rulesetPath is not valid"); 734 } 735 736 $ownSniffs = array(); 737 $includedSniffs = array(); 738 $excludedSniffs = array(); 739 $cliValues = $this->cli->getCommandLineValues(); 740 741 $rulesetDir = dirname($rulesetPath); 742 self::$rulesetDirs[] = $rulesetDir; 743 744 if (is_dir($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs') === true) { 745 if (PHP_CODESNIFFER_VERBOSITY > 1) { 746 echo str_repeat("\t", $depth); 747 echo "\tAdding sniff files from \"/.../".basename($rulesetDir)."/Sniffs/\" directory".PHP_EOL; 748 } 749 750 $ownSniffs = $this->_expandSniffDirectory($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs', $depth); 751 } 752 753 // Process custom sniff config settings. 754 foreach ($ruleset->{'config'} as $config) { 755 if ($this->_shouldProcessElement($config) === false) { 756 continue; 757 } 758 759 $this->setConfigData((string) $config['name'], (string) $config['value'], true); 760 if (PHP_CODESNIFFER_VERBOSITY > 1) { 761 echo str_repeat("\t", $depth); 762 echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL; 763 } 764 } 765 766 foreach ($ruleset->rule as $rule) { 767 if (isset($rule['ref']) === false 768 || $this->_shouldProcessElement($rule) === false 769 ) { 770 continue; 771 } 772 773 if (PHP_CODESNIFFER_VERBOSITY > 1) { 774 echo str_repeat("\t", $depth); 775 echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL; 776 } 777 778 $includedSniffs = array_merge( 779 $includedSniffs, 780 $this->_expandRulesetReference($rule['ref'], $rulesetDir, $depth) 781 ); 782 783 if (isset($rule->exclude) === true) { 784 foreach ($rule->exclude as $exclude) { 785 if ($this->_shouldProcessElement($exclude) === false) { 786 continue; 787 } 788 789 if (PHP_CODESNIFFER_VERBOSITY > 1) { 790 echo str_repeat("\t", $depth); 791 echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL; 792 } 793 794 // Check if a single code is being excluded, which is a shortcut 795 // for setting the severity of the message to 0. 796 $parts = explode('.', $exclude['name']); 797 if (count($parts) === 4) { 798 $this->ruleset[(string) $exclude['name']]['severity'] = 0; 799 if (PHP_CODESNIFFER_VERBOSITY > 1) { 800 echo str_repeat("\t", $depth); 801 echo "\t\t=> severity set to 0".PHP_EOL; 802 } 803 } else { 804 $excludedSniffs = array_merge( 805 $excludedSniffs, 806 $this->_expandRulesetReference($exclude['name'], $rulesetDir, ($depth + 1)) 807 ); 808 } 809 }//end foreach 810 }//end if 811 812 $this->_processRule($rule, $depth); 813 }//end foreach 814 815 // Process custom command line arguments. 816 $cliArgs = array(); 817 foreach ($ruleset->{'arg'} as $arg) { 818 if ($this->_shouldProcessElement($arg) === false) { 819 continue; 820 } 821 822 if (isset($arg['name']) === true) { 823 $argString = '--'.(string) $arg['name']; 824 if (isset($arg['value']) === true) { 825 $argString .= '='.(string) $arg['value']; 826 } 827 } else { 828 $argString = '-'.(string) $arg['value']; 829 } 830 831 $cliArgs[] = $argString; 832 833 if (PHP_CODESNIFFER_VERBOSITY > 1) { 834 echo str_repeat("\t", $depth); 835 echo "\t=> set command line value $argString".PHP_EOL; 836 } 837 }//end foreach 838 839 // Set custom php ini values as CLI args. 840 foreach ($ruleset->{'ini'} as $arg) { 841 if ($this->_shouldProcessElement($arg) === false) { 842 continue; 843 } 844 845 if (isset($arg['name']) === false) { 846 continue; 847 } 848 849 $name = (string) $arg['name']; 850 $argString = $name; 851 if (isset($arg['value']) === true) { 852 $value = (string) $arg['value']; 853 $argString .= "=$value"; 854 } else { 855 $value = 'true'; 856 } 857 858 $cliArgs[] = '-d'; 859 $cliArgs[] = $argString; 860 861 if (PHP_CODESNIFFER_VERBOSITY > 1) { 862 echo str_repeat("\t", $depth); 863 echo "\t=> set PHP ini value $name to $value".PHP_EOL; 864 } 865 }//end foreach 866 867 if (empty($cliValues['files']) === true && $cliValues['stdin'] === null) { 868 // Process hard-coded file paths. 869 foreach ($ruleset->{'file'} as $file) { 870 $file = (string) $file; 871 $cliArgs[] = $file; 872 if (PHP_CODESNIFFER_VERBOSITY > 1) { 873 echo str_repeat("\t", $depth); 874 echo "\t=> added \"$file\" to the file list".PHP_EOL; 875 } 876 } 877 } 878 879 if (empty($cliArgs) === false) { 880 // Change the directory so all relative paths are worked 881 // out based on the location of the ruleset instead of 882 // the location of the user. 883 $inPhar = self::isPharFile($rulesetDir); 884 if ($inPhar === false) { 885 $currentDir = getcwd(); 886 chdir($rulesetDir); 887 } 888 889 $this->cli->setCommandLineValues($cliArgs); 890 891 if ($inPhar === false) { 892 chdir($currentDir); 893 } 894 } 895 896 // Process custom ignore pattern rules. 897 foreach ($ruleset->{'exclude-pattern'} as $pattern) { 898 if ($this->_shouldProcessElement($pattern) === false) { 899 continue; 900 } 901 902 if (isset($pattern['type']) === false) { 903 $pattern['type'] = 'absolute'; 904 } 905 906 $this->ignorePatterns[(string) $pattern] = (string) $pattern['type']; 907 if (PHP_CODESNIFFER_VERBOSITY > 1) { 908 echo str_repeat("\t", $depth); 909 echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; 910 } 911 } 912 913 $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs)); 914 $excludedSniffs = array_unique($excludedSniffs); 915 916 if (PHP_CODESNIFFER_VERBOSITY > 1) { 917 $included = count($includedSniffs); 918 $excluded = count($excludedSniffs); 919 echo str_repeat("\t", $depth); 920 echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL; 921 } 922 923 // Merge our own sniff list with our externally included 924 // sniff list, but filter out any excluded sniffs. 925 $files = array(); 926 foreach ($includedSniffs as $sniff) { 927 if (in_array($sniff, $excludedSniffs) === true) { 928 continue; 929 } else { 930 $files[] = self::realpath($sniff); 931 } 932 } 933 934 return $files; 935 936 }//end processRuleset() 937 938 939 /** 940 * Expands a directory into a list of sniff files within. 941 * 942 * @param string $directory The path to a directory. 943 * @param int $depth How many nested processing steps we are in. This 944 * is only used for debug output. 945 * 946 * @return array 947 */ 948 private function _expandSniffDirectory($directory, $depth=0) 949 { 950 $sniffs = array(); 951 952 if (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') === true) { 953 $rdi = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::FOLLOW_SYMLINKS); 954 } else { 955 $rdi = new RecursiveDirectoryIterator($directory); 956 } 957 958 $di = new RecursiveIteratorIterator($rdi, 0, RecursiveIteratorIterator::CATCH_GET_CHILD); 959 960 $dirLen = strlen($directory); 961 962 foreach ($di as $file) { 963 $filename = $file->getFilename(); 964 965 // Skip hidden files. 966 if (substr($filename, 0, 1) === '.') { 967 continue; 968 } 969 970 // We are only interested in PHP and sniff files. 971 $fileParts = explode('.', $filename); 972 if (array_pop($fileParts) !== 'php') { 973 continue; 974 } 975 976 $basename = basename($filename, '.php'); 977 if (substr($basename, -5) !== 'Sniff') { 978 continue; 979 } 980 981 $path = $file->getPathname(); 982 983 // Skip files in hidden directories within the Sniffs directory of this 984 // standard. We use the offset with strpos() to allow hidden directories 985 // before, valid example: 986 // /home/foo/.composer/vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/... 987 if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) { 988 continue; 989 } 990 991 if (PHP_CODESNIFFER_VERBOSITY > 1) { 992 echo str_repeat("\t", $depth); 993 echo "\t\t=> $path".PHP_EOL; 994 } 995 996 $sniffs[] = $path; 997 }//end foreach 998 999 return $sniffs; 1000 1001 }//end _expandSniffDirectory() 1002 1003 1004 /** 1005 * Expands a ruleset reference into a list of sniff files. 1006 * 1007 * @param string $ref The reference from the ruleset XML file. 1008 * @param string $rulesetDir The directory of the ruleset XML file, used to 1009 * evaluate relative paths. 1010 * @param int $depth How many nested processing steps we are in. This 1011 * is only used for debug output. 1012 * 1013 * @return array 1014 * @throws PHP_CodeSniffer_Exception If the reference is invalid. 1015 */ 1016 private function _expandRulesetReference($ref, $rulesetDir, $depth=0) 1017 { 1018 // Ignore internal sniffs codes as they are used to only 1019 // hide and change internal messages. 1020 if (substr($ref, 0, 9) === 'Internal.') { 1021 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1022 echo str_repeat("\t", $depth); 1023 echo "\t\t* ignoring internal sniff code *".PHP_EOL; 1024 } 1025 1026 return array(); 1027 } 1028 1029 // As sniffs can't begin with a full stop, assume references in 1030 // this format are relative paths and attempt to convert them 1031 // to absolute paths. If this fails, let the reference run through 1032 // the normal checks and have it fail as normal. 1033 if (substr($ref, 0, 1) === '.') { 1034 $realpath = self::realpath($rulesetDir.'/'.$ref); 1035 if ($realpath !== false) { 1036 $ref = $realpath; 1037 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1038 echo str_repeat("\t", $depth); 1039 echo "\t\t=> $ref".PHP_EOL; 1040 } 1041 } 1042 } 1043 1044 // As sniffs can't begin with a tilde, assume references in 1045 // this format at relative to the user's home directory. 1046 if (substr($ref, 0, 2) === '~/') { 1047 $realpath = self::realpath($ref); 1048 if ($realpath !== false) { 1049 $ref = $realpath; 1050 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1051 echo str_repeat("\t", $depth); 1052 echo "\t\t=> $ref".PHP_EOL; 1053 } 1054 } 1055 } 1056 1057 if (is_file($ref) === true) { 1058 if (substr($ref, -9) === 'Sniff.php') { 1059 // A single external sniff. 1060 self::$rulesetDirs[] = dirname(dirname(dirname($ref))); 1061 return array($ref); 1062 } 1063 } else { 1064 // See if this is a whole standard being referenced. 1065 $path = $this->getInstalledStandardPath($ref); 1066 if (self::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { 1067 // If the ruleset exists inside the phar file, use it. 1068 if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { 1069 $path = $path.DIRECTORY_SEPARATOR.'ruleset.xml'; 1070 } else { 1071 $path = null; 1072 } 1073 } 1074 1075 if ($path !== null) { 1076 $ref = $path; 1077 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1078 echo str_repeat("\t", $depth); 1079 echo "\t\t=> $ref".PHP_EOL; 1080 } 1081 } else if (is_dir($ref) === false) { 1082 // Work out the sniff path. 1083 $sepPos = strpos($ref, DIRECTORY_SEPARATOR); 1084 if ($sepPos !== false) { 1085 $stdName = substr($ref, 0, $sepPos); 1086 $path = substr($ref, $sepPos); 1087 } else { 1088 $parts = explode('.', $ref); 1089 $stdName = $parts[0]; 1090 if (count($parts) === 1) { 1091 // A whole standard? 1092 $path = ''; 1093 } else if (count($parts) === 2) { 1094 // A directory of sniffs? 1095 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1]; 1096 } else { 1097 // A single sniff? 1098 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php'; 1099 } 1100 } 1101 1102 $newRef = false; 1103 $stdPath = $this->getInstalledStandardPath($stdName); 1104 if ($stdPath !== null && $path !== '') { 1105 if (self::isPharFile($stdPath) === true 1106 && strpos($stdPath, 'ruleset.xml') === false 1107 ) { 1108 // Phar files can only return the directory, 1109 // since ruleset can be omitted if building one standard. 1110 $newRef = self::realpath($stdPath.$path); 1111 } else { 1112 $newRef = self::realpath(dirname($stdPath).$path); 1113 } 1114 } 1115 1116 if ($newRef === false) { 1117 // The sniff is not locally installed, so check if it is being 1118 // referenced as a remote sniff outside the install. We do this 1119 // by looking through all directories where we have found ruleset 1120 // files before, looking for ones for this particular standard, 1121 // and seeing if it is in there. 1122 foreach (self::$rulesetDirs as $dir) { 1123 if (strtolower(basename($dir)) !== strtolower($stdName)) { 1124 continue; 1125 } 1126 1127 $newRef = self::realpath($dir.$path); 1128 1129 if ($newRef !== false) { 1130 $ref = $newRef; 1131 } 1132 } 1133 } else { 1134 $ref = $newRef; 1135 } 1136 1137 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1138 echo str_repeat("\t", $depth); 1139 echo "\t\t=> $ref".PHP_EOL; 1140 } 1141 }//end if 1142 }//end if 1143 1144 if (is_dir($ref) === true) { 1145 if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { 1146 // We are referencing an external coding standard. 1147 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1148 echo str_repeat("\t", $depth); 1149 echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL; 1150 } 1151 1152 return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2)); 1153 } else { 1154 // We are referencing a whole directory of sniffs. 1155 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1156 echo str_repeat("\t", $depth); 1157 echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL; 1158 echo str_repeat("\t", $depth); 1159 echo "\t\tAdding sniff files from directory".PHP_EOL; 1160 } 1161 1162 return $this->_expandSniffDirectory($ref, ($depth + 1)); 1163 } 1164 } else { 1165 if (is_file($ref) === false) { 1166 $error = "Referenced sniff \"$ref\" does not exist"; 1167 throw new PHP_CodeSniffer_Exception($error); 1168 } 1169 1170 if (substr($ref, -9) === 'Sniff.php') { 1171 // A single sniff. 1172 return array($ref); 1173 } else { 1174 // Assume an external ruleset.xml file. 1175 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1176 echo str_repeat("\t", $depth); 1177 echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL; 1178 } 1179 1180 return $this->processRuleset($ref, ($depth + 2)); 1181 } 1182 }//end if 1183 1184 }//end _expandRulesetReference() 1185 1186 1187 /** 1188 * Processes a rule from a ruleset XML file, overriding built-in defaults. 1189 * 1190 * @param SimpleXMLElement $rule The rule object from a ruleset XML file. 1191 * @param int $depth How many nested processing steps we are in. 1192 * This is only used for debug output. 1193 * 1194 * @return void 1195 */ 1196 private function _processRule($rule, $depth=0) 1197 { 1198 $code = (string) $rule['ref']; 1199 1200 // Custom severity. 1201 if (isset($rule->severity) === true 1202 && $this->_shouldProcessElement($rule->severity) === true 1203 ) { 1204 if (isset($this->ruleset[$code]) === false) { 1205 $this->ruleset[$code] = array(); 1206 } 1207 1208 $this->ruleset[$code]['severity'] = (int) $rule->severity; 1209 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1210 echo str_repeat("\t", $depth); 1211 echo "\t\t=> severity set to ".(int) $rule->severity.PHP_EOL; 1212 } 1213 } 1214 1215 // Custom message type. 1216 if (isset($rule->type) === true 1217 && $this->_shouldProcessElement($rule->type) === true 1218 ) { 1219 if (isset($this->ruleset[$code]) === false) { 1220 $this->ruleset[$code] = array(); 1221 } 1222 1223 $this->ruleset[$code]['type'] = (string) $rule->type; 1224 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1225 echo str_repeat("\t", $depth); 1226 echo "\t\t=> message type set to ".(string) $rule->type.PHP_EOL; 1227 } 1228 } 1229 1230 // Custom message. 1231 if (isset($rule->message) === true 1232 && $this->_shouldProcessElement($rule->message) === true 1233 ) { 1234 if (isset($this->ruleset[$code]) === false) { 1235 $this->ruleset[$code] = array(); 1236 } 1237 1238 $this->ruleset[$code]['message'] = (string) $rule->message; 1239 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1240 echo str_repeat("\t", $depth); 1241 echo "\t\t=> message set to ".(string) $rule->message.PHP_EOL; 1242 } 1243 } 1244 1245 // Custom properties. 1246 if (isset($rule->properties) === true 1247 && $this->_shouldProcessElement($rule->properties) === true 1248 ) { 1249 foreach ($rule->properties->property as $prop) { 1250 if ($this->_shouldProcessElement($prop) === false) { 1251 continue; 1252 } 1253 1254 if (isset($this->ruleset[$code]) === false) { 1255 $this->ruleset[$code] = array( 1256 'properties' => array(), 1257 ); 1258 } else if (isset($this->ruleset[$code]['properties']) === false) { 1259 $this->ruleset[$code]['properties'] = array(); 1260 } 1261 1262 $name = (string) $prop['name']; 1263 if (isset($prop['type']) === true 1264 && (string) $prop['type'] === 'array' 1265 ) { 1266 $value = (string) $prop['value']; 1267 $values = array(); 1268 foreach (explode(',', $value) as $val) { 1269 $v = ''; 1270 1271 list($k,$v) = explode('=>', $val.'=>'); 1272 if ($v !== '') { 1273 $values[$k] = $v; 1274 } else { 1275 $values[] = $k; 1276 } 1277 } 1278 1279 $this->ruleset[$code]['properties'][$name] = $values; 1280 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1281 echo str_repeat("\t", $depth); 1282 echo "\t\t=> array property \"$name\" set to \"$value\"".PHP_EOL; 1283 } 1284 } else { 1285 $this->ruleset[$code]['properties'][$name] = (string) $prop['value']; 1286 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1287 echo str_repeat("\t", $depth); 1288 echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'.PHP_EOL; 1289 } 1290 }//end if 1291 }//end foreach 1292 }//end if 1293 1294 // Ignore patterns. 1295 foreach ($rule->{'exclude-pattern'} as $pattern) { 1296 if ($this->_shouldProcessElement($pattern) === false) { 1297 continue; 1298 } 1299 1300 if (isset($this->ignorePatterns[$code]) === false) { 1301 $this->ignorePatterns[$code] = array(); 1302 } 1303 1304 if (isset($pattern['type']) === false) { 1305 $pattern['type'] = 'absolute'; 1306 } 1307 1308 $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type']; 1309 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1310 echo str_repeat("\t", $depth); 1311 echo "\t\t=> added sniff-specific ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; 1312 } 1313 } 1314 1315 }//end _processRule() 1316 1317 1318 /** 1319 * Determine if an element should be processed or ignored. 1320 * 1321 * @param SimpleXMLElement $element An object from a ruleset XML file. 1322 * @param int $depth How many nested processing steps we are in. 1323 * This is only used for debug output. 1324 * 1325 * @return bool 1326 */ 1327 private function _shouldProcessElement($element, $depth=0) 1328 { 1329 if (isset($element['phpcbf-only']) === false 1330 && isset($element['phpcs-only']) === false 1331 ) { 1332 // No exceptions are being made. 1333 return true; 1334 } 1335 1336 if (PHP_CODESNIFFER_CBF === true 1337 && isset($element['phpcbf-only']) === true 1338 && (string) $element['phpcbf-only'] === 'true' 1339 ) { 1340 return true; 1341 } 1342 1343 if (PHP_CODESNIFFER_CBF === false 1344 && isset($element['phpcs-only']) === true 1345 && (string) $element['phpcs-only'] === 'true' 1346 ) { 1347 return true; 1348 } 1349 1350 return false; 1351 1352 }//end _shouldProcessElement() 1353 1354 1355 /** 1356 * Loads and stores sniffs objects used for sniffing files. 1357 * 1358 * @param array $files Paths to the sniff files to register. 1359 * @param array $restrictions The sniff class names to restrict the allowed 1360 * listeners to. 1361 * @param array $exclusions The sniff class names to exclude from the 1362 * listeners list. 1363 * 1364 * @return void 1365 * @throws PHP_CodeSniffer_Exception If a sniff file path is invalid. 1366 */ 1367 public function registerSniffs($files, $restrictions, $exclusions) 1368 { 1369 $listeners = array(); 1370 1371 foreach ($files as $file) { 1372 // Work out where the position of /StandardName/Sniffs/... is 1373 // so we can determine what the class will be called. 1374 $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR); 1375 if ($sniffPos === false) { 1376 continue; 1377 } 1378 1379 $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR); 1380 if ($slashPos === false) { 1381 continue; 1382 } 1383 1384 $className = substr($file, ($slashPos + 1)); 1385 1386 if (substr_count($className, DIRECTORY_SEPARATOR) !== 3) { 1387 throw new PHP_CodeSniffer_Exception("Sniff file $className is not valid; sniff files must be located in a .../StandardName/Sniffs/CategoryName/ directory"); 1388 } 1389 1390 $className = substr($className, 0, -4); 1391 $className = str_replace(DIRECTORY_SEPARATOR, '_', $className); 1392 1393 // If they have specified a list of sniffs to restrict to, check 1394 // to see if this sniff is allowed. 1395 if (empty($restrictions) === false 1396 && in_array(strtolower($className), $restrictions) === false 1397 ) { 1398 continue; 1399 } 1400 1401 // If they have specified a list of sniffs to exclude, check 1402 // to see if this sniff is allowed. 1403 if (empty($exclusions) === false 1404 && in_array(strtolower($className), $exclusions) === true 1405 ) { 1406 continue; 1407 } 1408 1409 include_once $file; 1410 1411 // Support the use of PHP namespaces. If the class name we included 1412 // contains namespace separators instead of underscores, use this as the 1413 // class name from now on. 1414 $classNameNS = str_replace('_', '\\', $className); 1415 if (class_exists($classNameNS, false) === true) { 1416 $className = $classNameNS; 1417 } 1418 1419 // Skip abstract classes. 1420 $reflection = new ReflectionClass($className); 1421 if ($reflection->isAbstract() === true) { 1422 continue; 1423 } 1424 1425 $listeners[$className] = $className; 1426 1427 if (PHP_CODESNIFFER_VERBOSITY > 2) { 1428 echo "Registered $className".PHP_EOL; 1429 } 1430 }//end foreach 1431 1432 $this->sniffs = $listeners; 1433 1434 }//end registerSniffs() 1435 1436 1437 /** 1438 * Populates the array of PHP_CodeSniffer_Sniff's for this file. 1439 * 1440 * @return void 1441 * @throws PHP_CodeSniffer_Exception If sniff registration fails. 1442 */ 1443 public function populateTokenListeners() 1444 { 1445 // Construct a list of listeners indexed by token being listened for. 1446 $this->_tokenListeners = array(); 1447 1448 foreach ($this->sniffs as $listenerClass) { 1449 // Work out the internal code for this sniff. Detect usage of namespace 1450 // separators instead of underscores to support PHP namespaces. 1451 if (strstr($listenerClass, '\\') === false) { 1452 $parts = explode('_', $listenerClass); 1453 } else { 1454 $parts = explode('\\', $listenerClass); 1455 } 1456 1457 $code = $parts[0].'.'.$parts[2].'.'.$parts[3]; 1458 $code = substr($code, 0, -5); 1459 1460 $this->listeners[$listenerClass] = new $listenerClass(); 1461 $this->sniffCodes[$code] = $listenerClass; 1462 1463 // Set custom properties. 1464 if (isset($this->ruleset[$code]['properties']) === true) { 1465 foreach ($this->ruleset[$code]['properties'] as $name => $value) { 1466 $this->setSniffProperty($listenerClass, $name, $value); 1467 } 1468 } 1469 1470 $tokenizers = array(); 1471 $vars = get_class_vars($listenerClass); 1472 if (isset($vars['supportedTokenizers']) === true) { 1473 foreach ($vars['supportedTokenizers'] as $tokenizer) { 1474 $tokenizers[$tokenizer] = $tokenizer; 1475 } 1476 } else { 1477 $tokenizers = array('PHP' => 'PHP'); 1478 } 1479 1480 $tokens = $this->listeners[$listenerClass]->register(); 1481 if (is_array($tokens) === false) { 1482 $msg = "Sniff $listenerClass register() method must return an array"; 1483 throw new PHP_CodeSniffer_Exception($msg); 1484 } 1485 1486 $parts = explode('_', str_replace('\\', '_', $listenerClass)); 1487 $listenerSource = $parts[0].'.'.$parts[2].'.'.substr($parts[3], 0, -5); 1488 $ignorePatterns = array(); 1489 $patterns = $this->getIgnorePatterns($listenerSource); 1490 foreach ($patterns as $pattern => $type) { 1491 // While there is support for a type of each pattern 1492 // (absolute or relative) we don't actually support it here. 1493 $replacements = array( 1494 '\\,' => ',', 1495 '*' => '.*', 1496 ); 1497 1498 $ignorePatterns[] = strtr($pattern, $replacements); 1499 } 1500 1501 foreach ($tokens as $token) { 1502 if (isset($this->_tokenListeners[$token]) === false) { 1503 $this->_tokenListeners[$token] = array(); 1504 } 1505 1506 if (isset($this->_tokenListeners[$token][$listenerClass]) === false) { 1507 $this->_tokenListeners[$token][$listenerClass] = array( 1508 'class' => $listenerClass, 1509 'source' => $listenerSource, 1510 'tokenizers' => $tokenizers, 1511 'ignore' => $ignorePatterns, 1512 ); 1513 } 1514 } 1515 }//end foreach 1516 1517 }//end populateTokenListeners() 1518 1519 1520 /** 1521 * Set a single property for a sniff. 1522 * 1523 * @param string $listenerClass The class name of the sniff. 1524 * @param string $name The name of the property to change. 1525 * @param string $value The new value of the property. 1526 * 1527 * @return void 1528 */ 1529 public function setSniffProperty($listenerClass, $name, $value) 1530 { 1531 // Setting a property for a sniff we are not using. 1532 if (isset($this->listeners[$listenerClass]) === false) { 1533 return; 1534 } 1535 1536 $name = trim($name); 1537 if (is_string($value) === true) { 1538 $value = trim($value); 1539 } 1540 1541 // Special case for booleans. 1542 if ($value === 'true') { 1543 $value = true; 1544 } else if ($value === 'false') { 1545 $value = false; 1546 } 1547 1548 $this->listeners[$listenerClass]->$name = $value; 1549 1550 }//end setSniffProperty() 1551 1552 1553 /** 1554 * Get a list of files that will be processed. 1555 * 1556 * If passed directories, this method will find all files within them. 1557 * The method will also perform file extension and ignore pattern filtering. 1558 * 1559 * @param string $paths A list of file or directory paths to process. 1560 * @param boolean $local If true, only process 1 level of files in directories 1561 * 1562 * @return array 1563 * @throws Exception If there was an error opening a directory. 1564 * @see shouldProcessFile() 1565 */ 1566 public function getFilesToProcess($paths, $local=false) 1567 { 1568 $files = array(); 1569 1570 foreach ($paths as $path) { 1571 if (is_dir($path) === true || self::isPharFile($path) === true) { 1572 if (self::isPharFile($path) === true) { 1573 $path = 'phar://'.$path; 1574 } 1575 1576 if ($local === true) { 1577 $di = new DirectoryIterator($path); 1578 } else { 1579 $di = new RecursiveIteratorIterator( 1580 new RecursiveDirectoryIterator($path), 1581 0, 1582 RecursiveIteratorIterator::CATCH_GET_CHILD 1583 ); 1584 } 1585 1586 foreach ($di as $file) { 1587 // Check if the file exists after all symlinks are resolved. 1588 $filePath = self::realpath($file->getPathname()); 1589 if ($filePath === false) { 1590 continue; 1591 } 1592 1593 if (is_dir($filePath) === true) { 1594 continue; 1595 } 1596 1597 if ($this->shouldProcessFile($file->getPathname(), $path) === false) { 1598 continue; 1599 } 1600 1601 $files[] = $file->getPathname(); 1602 }//end foreach 1603 } else { 1604 if ($this->shouldIgnoreFile($path, dirname($path)) === true) { 1605 continue; 1606 } 1607 1608 $files[] = $path; 1609 }//end if 1610 }//end foreach 1611 1612 return $files; 1613 1614 }//end getFilesToProcess() 1615 1616 1617 /** 1618 * Checks filtering rules to see if a file should be checked. 1619 * 1620 * Checks both file extension filters and path ignore filters. 1621 * 1622 * @param string $path The path to the file being checked. 1623 * @param string $basedir The directory to use for relative path checks. 1624 * 1625 * @return bool 1626 */ 1627 public function shouldProcessFile($path, $basedir) 1628 { 1629 // Check that the file's extension is one we are checking. 1630 // We are strict about checking the extension and we don't 1631 // let files through with no extension or that start with a dot. 1632 $fileName = basename($path); 1633 $fileParts = explode('.', $fileName); 1634 if ($fileParts[0] === $fileName || $fileParts[0] === '') { 1635 return false; 1636 } 1637 1638 // Checking multi-part file extensions, so need to create a 1639 // complete extension list and make sure one is allowed. 1640 $extensions = array(); 1641 array_shift($fileParts); 1642 foreach ($fileParts as $part) { 1643 $extensions[implode('.', $fileParts)] = 1; 1644 array_shift($fileParts); 1645 } 1646 1647 $matches = array_intersect_key($extensions, $this->allowedFileExtensions); 1648 if (empty($matches) === true) { 1649 return false; 1650 } 1651 1652 // If the file's path matches one of our ignore patterns, skip it. 1653 if ($this->shouldIgnoreFile($path, $basedir) === true) { 1654 return false; 1655 } 1656 1657 return true; 1658 1659 }//end shouldProcessFile() 1660 1661 1662 /** 1663 * Checks filtering rules to see if a file should be ignored. 1664 * 1665 * @param string $path The path to the file being checked. 1666 * @param string $basedir The directory to use for relative path checks. 1667 * 1668 * @return bool 1669 */ 1670 public function shouldIgnoreFile($path, $basedir) 1671 { 1672 $relativePath = $path; 1673 if (strpos($path, $basedir) === 0) { 1674 // The +1 cuts off the directory separator as well. 1675 $relativePath = substr($path, (strlen($basedir) + 1)); 1676 } 1677 1678 foreach ($this->ignorePatterns as $pattern => $type) { 1679 if (is_array($type) === true) { 1680 // A sniff specific ignore pattern. 1681 continue; 1682 } 1683 1684 // Maintains backwards compatibility in case the ignore pattern does 1685 // not have a relative/absolute value. 1686 if (is_int($pattern) === true) { 1687 $pattern = $type; 1688 $type = 'absolute'; 1689 } 1690 1691 $replacements = array( 1692 '\\,' => ',', 1693 '*' => '.*', 1694 ); 1695 1696 // We assume a / directory separator, as do the exclude rules 1697 // most developers write, so we need a special case for any system 1698 // that is different. 1699 if (DIRECTORY_SEPARATOR === '\\') { 1700 $replacements['/'] = '\\\\'; 1701 } 1702 1703 $pattern = strtr($pattern, $replacements); 1704 1705 if ($type === 'relative') { 1706 $testPath = $relativePath; 1707 } else { 1708 $testPath = $path; 1709 } 1710 1711 $pattern = '`'.$pattern.'`i'; 1712 if (preg_match($pattern, $testPath) === 1) { 1713 return true; 1714 } 1715 }//end foreach 1716 1717 return false; 1718 1719 }//end shouldIgnoreFile() 1720 1721 1722 /** 1723 * Run the code sniffs over a single given file. 1724 * 1725 * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it 1726 * conforms with the standard. Returns the processed file object, or NULL 1727 * if no file was processed due to error. 1728 * 1729 * @param string $file The file to process. 1730 * @param string $contents The contents to parse. If NULL, the content 1731 * is taken from the file system. 1732 * 1733 * @return PHP_CodeSniffer_File 1734 * @throws PHP_CodeSniffer_Exception If the file could not be processed. 1735 * @see _processFile() 1736 */ 1737 public function processFile($file, $contents=null) 1738 { 1739 if ($contents === null && file_exists($file) === false) { 1740 throw new PHP_CodeSniffer_Exception("Source file $file does not exist"); 1741 } 1742 1743 $filePath = self::realpath($file); 1744 if ($filePath === false) { 1745 $filePath = $file; 1746 } 1747 1748 // Before we go and spend time tokenizing this file, just check 1749 // to see if there is a tag up top to indicate that the whole 1750 // file should be ignored. It must be on one of the first two lines. 1751 $firstContent = $contents; 1752 if ($contents === null && is_readable($filePath) === true) { 1753 $handle = fopen($filePath, 'r'); 1754 stream_set_blocking($handle, true); 1755 if ($handle !== false) { 1756 $firstContent = fgets($handle); 1757 $firstContent .= fgets($handle); 1758 fclose($handle); 1759 1760 if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) { 1761 // We are ignoring the whole file. 1762 if (PHP_CODESNIFFER_VERBOSITY > 0) { 1763 echo 'Ignoring '.basename($filePath).PHP_EOL; 1764 } 1765 1766 return null; 1767 } 1768 } 1769 }//end if 1770 1771 try { 1772 $phpcsFile = $this->_processFile($file, $contents); 1773 } catch (Exception $e) { 1774 $trace = $e->getTrace(); 1775 1776 $filename = $trace[0]['args'][0]; 1777 if (is_object($filename) === true 1778 && get_class($filename) === 'PHP_CodeSniffer_File' 1779 ) { 1780 $filename = $filename->getFilename(); 1781 } else if (is_numeric($filename) === true) { 1782 // See if we can find the PHP_CodeSniffer_File object. 1783 foreach ($trace as $data) { 1784 if (isset($data['args'][0]) === true 1785 && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true 1786 ) { 1787 $filename = $data['args'][0]->getFilename(); 1788 } 1789 } 1790 } else if (is_string($filename) === false) { 1791 $filename = (string) $filename; 1792 } 1793 1794 $errorMessage = '"'.$e->getMessage().'" at '.$e->getFile().':'.$e->getLine(); 1795 $error = "An error occurred during processing; checking has been aborted. The error message was: $errorMessage"; 1796 1797 $phpcsFile = new PHP_CodeSniffer_File( 1798 $filename, 1799 $this->_tokenListeners, 1800 $this->ruleset, 1801 $this 1802 ); 1803 1804 $phpcsFile->addError($error, null, 'Internal.Exception'); 1805 }//end try 1806 1807 $cliValues = $this->cli->getCommandLineValues(); 1808 1809 if (PHP_CODESNIFFER_INTERACTIVE === false) { 1810 // Cache the report data for this file so we can unset it to save memory. 1811 $this->reporting->cacheFileReport($phpcsFile, $cliValues); 1812 $phpcsFile->cleanUp(); 1813 return $phpcsFile; 1814 } 1815 1816 /* 1817 Running interactively. 1818 Print the error report for the current file and then wait for user input. 1819 */ 1820 1821 // Get current violations and then clear the list to make sure 1822 // we only print violations for a single file each time. 1823 $numErrors = null; 1824 while ($numErrors !== 0) { 1825 $numErrors = ($phpcsFile->getErrorCount() + $phpcsFile->getWarningCount()); 1826 if ($numErrors === 0) { 1827 continue; 1828 } 1829 1830 $reportClass = $this->reporting->factory('full'); 1831 $reportData = $this->reporting->prepareFileReport($phpcsFile); 1832 $reportClass->generateFileReport($reportData, $phpcsFile, $cliValues['showSources'], $cliValues['reportWidth']); 1833 1834 echo '<ENTER> to recheck, [s] to skip or [q] to quit : '; 1835 $input = fgets(STDIN); 1836 $input = trim($input); 1837 1838 switch ($input) { 1839 case 's': 1840 break(2); 1841 case 'q': 1842 exit(0); 1843 break; 1844 default: 1845 // Repopulate the sniffs because some of them save their state 1846 // and only clear it when the file changes, but we are rechecking 1847 // the same file. 1848 $this->populateTokenListeners(); 1849 $phpcsFile = $this->_processFile($file, $contents); 1850 break; 1851 } 1852 }//end while 1853 1854 return $phpcsFile; 1855 1856 }//end processFile() 1857 1858 1859 /** 1860 * Process the sniffs for a single file. 1861 * 1862 * Does raw processing only. No interactive support or error checking. 1863 * 1864 * @param string $file The file to process. 1865 * @param string $contents The contents to parse. If NULL, the content 1866 * is taken from the file system. 1867 * 1868 * @return PHP_CodeSniffer_File 1869 * @see processFile() 1870 */ 1871 private function _processFile($file, $contents) 1872 { 1873 $stdin = false; 1874 $cliValues = $this->cli->getCommandLineValues(); 1875 if (empty($cliValues['files']) === true) { 1876 $stdin = true; 1877 } 1878 1879 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { 1880 $startTime = microtime(true); 1881 echo 'Processing '.basename($file).' '; 1882 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1883 echo PHP_EOL; 1884 } 1885 } 1886 1887 $phpcsFile = new PHP_CodeSniffer_File( 1888 $file, 1889 $this->_tokenListeners, 1890 $this->ruleset, 1891 $this 1892 ); 1893 1894 $phpcsFile->start($contents); 1895 1896 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { 1897 $timeTaken = ((microtime(true) - $startTime) * 1000); 1898 if ($timeTaken < 1000) { 1899 $timeTaken = round($timeTaken); 1900 echo "DONE in {$timeTaken}ms"; 1901 } else { 1902 $timeTaken = round(($timeTaken / 1000), 2); 1903 echo "DONE in $timeTaken secs"; 1904 } 1905 1906 if (PHP_CODESNIFFER_CBF === true) { 1907 $errors = $phpcsFile->getFixableCount(); 1908 echo " ($errors fixable violations)".PHP_EOL; 1909 } else { 1910 $errors = $phpcsFile->getErrorCount(); 1911 $warnings = $phpcsFile->getWarningCount(); 1912 echo " ($errors errors, $warnings warnings)".PHP_EOL; 1913 } 1914 } 1915 1916 return $phpcsFile; 1917 1918 }//end _processFile() 1919 1920 1921 /** 1922 * Generates documentation for a coding standard. 1923 * 1924 * @param string $standard The standard to generate docs for 1925 * @param array $sniffs A list of sniffs to limit the docs to. 1926 * @param string $generator The name of the generator class to use. 1927 * 1928 * @return void 1929 */ 1930 public function generateDocs($standard, array $sniffs=array(), $generator='Text') 1931 { 1932 if (class_exists('PHP_CodeSniffer_DocGenerators_'.$generator, true) === false) { 1933 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_DocGenerators_'.$generator.' not found'); 1934 } 1935 1936 $class = "PHP_CodeSniffer_DocGenerators_$generator"; 1937 $generator = new $class($standard, $sniffs); 1938 1939 $generator->generate(); 1940 1941 }//end generateDocs() 1942 1943 1944 /** 1945 * Gets the array of PHP_CodeSniffer_Sniff's. 1946 * 1947 * @return PHP_CodeSniffer_Sniff[] 1948 */ 1949 public function getSniffs() 1950 { 1951 return $this->listeners; 1952 1953 }//end getSniffs() 1954 1955 1956 /** 1957 * Gets the array of PHP_CodeSniffer_Sniff's indexed by token type. 1958 * 1959 * @return array 1960 */ 1961 public function getTokenSniffs() 1962 { 1963 return $this->_tokenListeners; 1964 1965 }//end getTokenSniffs() 1966 1967 1968 /** 1969 * Returns true if the specified string is in the camel caps format. 1970 * 1971 * @param string $string The string the verify. 1972 * @param boolean $classFormat If true, check to see if the string is in the 1973 * class format. Class format strings must start 1974 * with a capital letter and contain no 1975 * underscores. 1976 * @param boolean $public If true, the first character in the string 1977 * must be an a-z character. If false, the 1978 * character must be an underscore. This 1979 * argument is only applicable if $classFormat 1980 * is false. 1981 * @param boolean $strict If true, the string must not have two capital 1982 * letters next to each other. If false, a 1983 * relaxed camel caps policy is used to allow 1984 * for acronyms. 1985 * 1986 * @return boolean 1987 */ 1988 public static function isCamelCaps( 1989 $string, 1990 $classFormat=false, 1991 $public=true, 1992 $strict=true 1993 ) { 1994 // Check the first character first. 1995 if ($classFormat === false) { 1996 $legalFirstChar = ''; 1997 if ($public === false) { 1998 $legalFirstChar = '[_]'; 1999 } 2000 2001 if ($strict === false) { 2002 // Can either start with a lowercase letter, or multiple uppercase 2003 // in a row, representing an acronym. 2004 $legalFirstChar .= '([A-Z]{2,}|[a-z])'; 2005 } else { 2006 $legalFirstChar .= '[a-z]'; 2007 } 2008 } else { 2009 $legalFirstChar = '[A-Z]'; 2010 } 2011 2012 if (preg_match("/^$legalFirstChar/", $string) === 0) { 2013 return false; 2014 } 2015 2016 // Check that the name only contains legal characters. 2017 $legalChars = 'a-zA-Z0-9'; 2018 if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { 2019 return false; 2020 } 2021 2022 if ($strict === true) { 2023 // Check that there are not two capital letters next to each other. 2024 $length = strlen($string); 2025 $lastCharWasCaps = $classFormat; 2026 2027 for ($i = 1; $i < $length; $i++) { 2028 $ascii = ord($string{$i}); 2029 if ($ascii >= 48 && $ascii <= 57) { 2030 // The character is a number, so it cant be a capital. 2031 $isCaps = false; 2032 } else { 2033 if (strtoupper($string{$i}) === $string{$i}) { 2034 $isCaps = true; 2035 } else { 2036 $isCaps = false; 2037 } 2038 } 2039 2040 if ($isCaps === true && $lastCharWasCaps === true) { 2041 return false; 2042 } 2043 2044 $lastCharWasCaps = $isCaps; 2045 } 2046 }//end if 2047 2048 return true; 2049 2050 }//end isCamelCaps() 2051 2052 2053 /** 2054 * Returns true if the specified string is in the underscore caps format. 2055 * 2056 * @param string $string The string to verify. 2057 * 2058 * @return boolean 2059 */ 2060 public static function isUnderscoreName($string) 2061 { 2062 // If there are space in the name, it can't be valid. 2063 if (strpos($string, ' ') !== false) { 2064 return false; 2065 } 2066 2067 $validName = true; 2068 $nameBits = explode('_', $string); 2069 2070 if (preg_match('|^[A-Z]|', $string) === 0) { 2071 // Name does not begin with a capital letter. 2072 $validName = false; 2073 } else { 2074 foreach ($nameBits as $bit) { 2075 if ($bit === '') { 2076 continue; 2077 } 2078 2079 if ($bit{0} !== strtoupper($bit{0})) { 2080 $validName = false; 2081 break; 2082 } 2083 } 2084 } 2085 2086 return $validName; 2087 2088 }//end isUnderscoreName() 2089 2090 2091 /** 2092 * Returns a valid variable type for param/var tag. 2093 * 2094 * If type is not one of the standard type, it must be a custom type. 2095 * Returns the correct type name suggestion if type name is invalid. 2096 * 2097 * @param string $varType The variable type to process. 2098 * 2099 * @return string 2100 */ 2101 public static function suggestType($varType) 2102 { 2103 if ($varType === '') { 2104 return ''; 2105 } 2106 2107 if (in_array($varType, self::$allowedTypes) === true) { 2108 return $varType; 2109 } else { 2110 $lowerVarType = strtolower($varType); 2111 switch ($lowerVarType) { 2112 case 'bool': 2113 case 'boolean': 2114 return 'boolean'; 2115 case 'double': 2116 case 'real': 2117 case 'float': 2118 return 'float'; 2119 case 'int': 2120 case 'integer': 2121 return 'integer'; 2122 case 'array()': 2123 case 'array': 2124 return 'array'; 2125 }//end switch 2126 2127 if (strpos($lowerVarType, 'array(') !== false) { 2128 // Valid array declaration: 2129 // array, array(type), array(type1 => type2). 2130 $matches = array(); 2131 $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; 2132 if (preg_match($pattern, $varType, $matches) !== 0) { 2133 $type1 = ''; 2134 if (isset($matches[1]) === true) { 2135 $type1 = $matches[1]; 2136 } 2137 2138 $type2 = ''; 2139 if (isset($matches[3]) === true) { 2140 $type2 = $matches[3]; 2141 } 2142 2143 $type1 = self::suggestType($type1); 2144 $type2 = self::suggestType($type2); 2145 if ($type2 !== '') { 2146 $type2 = ' => '.$type2; 2147 } 2148 2149 return "array($type1$type2)"; 2150 } else { 2151 return 'array'; 2152 }//end if 2153 } else if (in_array($lowerVarType, self::$allowedTypes) === true) { 2154 // A valid type, but not lower cased. 2155 return $lowerVarType; 2156 } else { 2157 // Must be a custom type name. 2158 return $varType; 2159 }//end if 2160 }//end if 2161 2162 }//end suggestType() 2163 2164 2165 /** 2166 * Prepares token content for output to screen. 2167 * 2168 * Replaces invisible characters so they are visible. On non-Windows 2169 * OSes it will also colour the invisible characters. 2170 * 2171 * @param string $content The content to prepare. 2172 * 2173 * @return string 2174 */ 2175 public static function prepareForOutput($content) 2176 { 2177 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 2178 $content = str_replace("\r", '\r', $content); 2179 $content = str_replace("\n", '\n', $content); 2180 $content = str_replace("\t", '\t', $content); 2181 } else { 2182 $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content); 2183 $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content); 2184 $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content); 2185 $content = str_replace(' ', "\033[30;1m·\033[0m", $content); 2186 } 2187 2188 return $content; 2189 2190 }//end prepareForOutput() 2191 2192 2193 /** 2194 * Get a list paths where standards are installed. 2195 * 2196 * @return array 2197 */ 2198 public static function getInstalledStandardPaths() 2199 { 2200 $installedPaths = array(dirname(__FILE__).DIRECTORY_SEPARATOR.'CodeSniffer'.DIRECTORY_SEPARATOR.'Standards'); 2201 $configPaths = PHP_CodeSniffer::getConfigData('installed_paths'); 2202 if ($configPaths !== null) { 2203 $installedPaths = array_merge($installedPaths, explode(',', $configPaths)); 2204 } 2205 2206 $resolvedInstalledPaths = array(); 2207 foreach ($installedPaths as $installedPath) { 2208 if (substr($installedPath, 0, 1) === '.') { 2209 $installedPath = dirname(__FILE__).DIRECTORY_SEPARATOR.$installedPath; 2210 } 2211 2212 $resolvedInstalledPaths[] = $installedPath; 2213 } 2214 2215 return $resolvedInstalledPaths; 2216 2217 }//end getInstalledStandardPaths() 2218 2219 2220 /** 2221 * Get a list of all coding standards installed. 2222 * 2223 * Coding standards are directories located in the 2224 * CodeSniffer/Standards directory. Valid coding standards 2225 * include a Sniffs subdirectory. 2226 * 2227 * @param boolean $includeGeneric If true, the special "Generic" 2228 * coding standard will be included 2229 * if installed. 2230 * @param string $standardsDir A specific directory to look for standards 2231 * in. If not specified, PHP_CodeSniffer will 2232 * look in its default locations. 2233 * 2234 * @return array 2235 * @see isInstalledStandard() 2236 */ 2237 public static function getInstalledStandards( 2238 $includeGeneric=false, 2239 $standardsDir='' 2240 ) { 2241 $installedStandards = array(); 2242 2243 if ($standardsDir === '') { 2244 $installedPaths = self::getInstalledStandardPaths(); 2245 } else { 2246 $installedPaths = array($standardsDir); 2247 } 2248 2249 foreach ($installedPaths as $standardsDir) { 2250 $di = new DirectoryIterator($standardsDir); 2251 foreach ($di as $file) { 2252 if ($file->isDir() === true && $file->isDot() === false) { 2253 $filename = $file->getFilename(); 2254 2255 // Ignore the special "Generic" standard. 2256 if ($includeGeneric === false && $filename === 'Generic') { 2257 continue; 2258 } 2259 2260 // Valid coding standard dirs include a ruleset. 2261 $csFile = $file->getPathname().'/ruleset.xml'; 2262 if (is_file($csFile) === true) { 2263 $installedStandards[] = $filename; 2264 } 2265 } 2266 } 2267 }//end foreach 2268 2269 return $installedStandards; 2270 2271 }//end getInstalledStandards() 2272 2273 2274 /** 2275 * Determine if a standard is installed. 2276 * 2277 * Coding standards are directories located in the 2278 * CodeSniffer/Standards directory. Valid coding standards 2279 * include a ruleset.xml file. 2280 * 2281 * @param string $standard The name of the coding standard. 2282 * 2283 * @return boolean 2284 * @see getInstalledStandards() 2285 */ 2286 public static function isInstalledStandard($standard) 2287 { 2288 $path = self::getInstalledStandardPath($standard); 2289 if ($path !== null && strpos($path, 'ruleset.xml') !== false) { 2290 return true; 2291 } else { 2292 // This could be a custom standard, installed outside our 2293 // standards directory. 2294 $standard = self::realPath($standard); 2295 2296 // Might be an actual ruleset file itself. 2297 // If it has an XML extension, let's at least try it. 2298 if (is_file($standard) === true 2299 && (substr(strtolower($standard), -4) === '.xml' 2300 || substr(strtolower($standard), -9) === '.xml.dist') 2301 ) { 2302 return true; 2303 } 2304 2305 // If it is a directory with a ruleset.xml file in it, 2306 // it is a standard. 2307 $ruleset = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.'ruleset.xml'; 2308 if (is_file($ruleset) === true) { 2309 return true; 2310 } 2311 }//end if 2312 2313 return false; 2314 2315 }//end isInstalledStandard() 2316 2317 2318 /** 2319 * Return the path of an installed coding standard. 2320 * 2321 * Coding standards are directories located in the 2322 * CodeSniffer/Standards directory. Valid coding standards 2323 * include a ruleset.xml file. 2324 * 2325 * @param string $standard The name of the coding standard. 2326 * 2327 * @return string|null 2328 */ 2329 public static function getInstalledStandardPath($standard) 2330 { 2331 $installedPaths = self::getInstalledStandardPaths(); 2332 foreach ($installedPaths as $installedPath) { 2333 $standardPath = $installedPath.DIRECTORY_SEPARATOR.$standard; 2334 $path = self::realpath($standardPath.DIRECTORY_SEPARATOR.'ruleset.xml'); 2335 if (is_file($path) === true) { 2336 return $path; 2337 } else if (self::isPharFile($standardPath) === true) { 2338 $path = self::realpath($standardPath); 2339 if ($path !== false) { 2340 return $path; 2341 } 2342 } 2343 } 2344 2345 return null; 2346 2347 }//end getInstalledStandardPath() 2348 2349 2350 /** 2351 * Get a single config value. 2352 * 2353 * Config data is stored in the data dir, in a file called 2354 * CodeSniffer.conf. It is a simple PHP array. 2355 * 2356 * @param string $key The name of the config value. 2357 * 2358 * @return string|null 2359 * @see setConfigData() 2360 * @see getAllConfigData() 2361 */ 2362 public static function getConfigData($key) 2363 { 2364 $phpCodeSnifferConfig = self::getAllConfigData(); 2365 2366 if ($phpCodeSnifferConfig === null) { 2367 return null; 2368 } 2369 2370 if (isset($phpCodeSnifferConfig[$key]) === false) { 2371 return null; 2372 } 2373 2374 return $phpCodeSnifferConfig[$key]; 2375 2376 }//end getConfigData() 2377 2378 2379 /** 2380 * Set a single config value. 2381 * 2382 * Config data is stored in the data dir, in a file called 2383 * CodeSniffer.conf. It is a simple PHP array. 2384 * 2385 * @param string $key The name of the config value. 2386 * @param string|null $value The value to set. If null, the config 2387 * entry is deleted, reverting it to the 2388 * default value. 2389 * @param boolean $temp Set this config data temporarily for this 2390 * script run. This will not write the config 2391 * data to the config file. 2392 * 2393 * @return boolean 2394 * @see getConfigData() 2395 * @throws PHP_CodeSniffer_Exception If the config file can not be written. 2396 */ 2397 public static function setConfigData($key, $value, $temp=false) 2398 { 2399 if ($temp === false) { 2400 $path = ''; 2401 if (is_callable('Phar::running') === true) { 2402 $path = Phar::running(false); 2403 } 2404 2405 if ($path !== '') { 2406 $configFile = dirname($path).'/CodeSniffer.conf'; 2407 } else { 2408 $configFile = dirname(__FILE__).'/CodeSniffer.conf'; 2409 if (is_file($configFile) === false 2410 && strpos('@data_dir@', '@data_dir') === false 2411 ) { 2412 // If data_dir was replaced, this is a PEAR install and we can 2413 // use the PEAR data dir to store the conf file. 2414 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; 2415 } 2416 } 2417 2418 if (is_file($configFile) === true 2419 && is_writable($configFile) === false 2420 ) { 2421 $error = 'Config file '.$configFile.' is not writable'; 2422 throw new PHP_CodeSniffer_Exception($error); 2423 } 2424 }//end if 2425 2426 $phpCodeSnifferConfig = self::getAllConfigData(); 2427 2428 if ($value === null) { 2429 if (isset($phpCodeSnifferConfig[$key]) === true) { 2430 unset($phpCodeSnifferConfig[$key]); 2431 } 2432 } else { 2433 $phpCodeSnifferConfig[$key] = $value; 2434 } 2435 2436 if ($temp === false) { 2437 $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; 2438 $output .= var_export($phpCodeSnifferConfig, true); 2439 $output .= "\n?".'>'; 2440 2441 if (file_put_contents($configFile, $output) === false) { 2442 return false; 2443 } 2444 } 2445 2446 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; 2447 2448 return true; 2449 2450 }//end setConfigData() 2451 2452 2453 /** 2454 * Get all config data in an array. 2455 * 2456 * @return array<string, string> 2457 * @see getConfigData() 2458 */ 2459 public static function getAllConfigData() 2460 { 2461 if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) { 2462 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; 2463 } 2464 2465 $path = ''; 2466 if (is_callable('Phar::running') === true) { 2467 $path = Phar::running(false); 2468 } 2469 2470 if ($path !== '') { 2471 $configFile = dirname($path).'/CodeSniffer.conf'; 2472 } else { 2473 $configFile = dirname(__FILE__).'/CodeSniffer.conf'; 2474 if (is_file($configFile) === false) { 2475 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; 2476 } 2477 } 2478 2479 if (is_file($configFile) === false) { 2480 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = array(); 2481 return array(); 2482 } 2483 2484 include $configFile; 2485 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; 2486 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; 2487 2488 }//end getAllConfigData() 2489 2490 2491 /** 2492 * Return TRUE, if the path is a phar file. 2493 * 2494 * @param string $path The path to use. 2495 * 2496 * @return mixed 2497 */ 2498 public static function isPharFile($path) 2499 { 2500 if (strpos($path, 'phar://') === 0) { 2501 return true; 2502 } 2503 2504 return false; 2505 2506 }//end isPharFile() 2507 2508 2509 /** 2510 * CodeSniffer alternative for realpath. 2511 * 2512 * Allows for phar support. 2513 * 2514 * @param string $path The path to use. 2515 * 2516 * @return mixed 2517 */ 2518 public static function realpath($path) 2519 { 2520 // Support the path replacement of ~ with the user's home directory. 2521 if (substr($path, 0, 2) === '~/') { 2522 $homeDir = getenv('HOME'); 2523 if ($homeDir !== false) { 2524 $path = $homeDir.substr($path, 1); 2525 } 2526 } 2527 2528 // No extra work needed if this is not a phar file. 2529 if (self::isPharFile($path) === false) { 2530 return realpath($path); 2531 } 2532 2533 // Before trying to break down the file path, 2534 // check if it exists first because it will mostly not 2535 // change after running the below code. 2536 if (file_exists($path) === true) { 2537 return $path; 2538 } 2539 2540 $phar = Phar::running(false); 2541 $extra = str_replace('phar://'.$phar, '', $path); 2542 $path = realpath($phar); 2543 if ($path === false) { 2544 return false; 2545 } 2546 2547 $path = 'phar://'.$path.$extra; 2548 if (file_exists($path) === true) { 2549 return $path; 2550 } 2551 2552 return false; 2553 2554 }//end realpath() 2555 2556 2557 /** 2558 * CodeSniffer alternative for chdir(). 2559 * 2560 * Allows for phar support. 2561 * 2562 * @param string $path The path to use. 2563 * 2564 * @return void 2565 */ 2566 public static function chdir($path) 2567 { 2568 if (self::isPharFile($path) === true) { 2569 $phar = Phar::running(false); 2570 chdir(dirname($phar)); 2571 } else { 2572 chdir($path); 2573 } 2574 2575 }//end chdir() 2576 2577 2578}//end class 2579