GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 699b70...879176 )
by Chris
13:23
created

PHP_CodeSniffer::_processRule()   F

Complexity

Conditions 30
Paths 3000

Size

Total Lines 120
Code Lines 68

Duplication

Lines 47
Ratio 39.17 %

Importance

Changes 0
Metric Value
cc 30
eloc 68
nc 3000
nop 2
dl 47
loc 120
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 68 and the first side effect is on line 17.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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 <[email protected]>
11
 * @author    Marc McIntyre <[email protected]>
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
17
spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
18
19
if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
20
    throw new Exception('Class PHP_CodeSniffer_Exception not found');
21
}
22
23
if (class_exists('PHP_CodeSniffer_File', true) === false) {
24
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
25
}
26
27
if (class_exists('PHP_CodeSniffer_Fixer', true) === false) {
28
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Fixer not found');
29
}
30
31
if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
32
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
33
}
34
35
if (class_exists('PHP_CodeSniffer_CLI', true) === false) {
36
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found');
37
}
38
39
if (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 <[email protected]>
62
 * @author    Marc McIntyre <[email protected]>
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
 */
68
class PHP_CodeSniffer
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
69
{
70
71
    /**
72
     * The current version.
73
     *
74
     * @var string
75
     */
76
    const VERSION = '2.7.1';
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;
0 ignored issues
show
Documentation Bug introduced by
The property $errorSeverity was declared of type boolean, but PHPCS_DEFAULT_ERROR_SEV is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
265
        $this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
0 ignored issues
show
Documentation Bug introduced by
The property $warningSeverity was declared of type boolean, but PHPCS_DEFAULT_WARN_SEV is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression self::$rulesetDirs of type string is not traversable.
Loading history...
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;
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $rulesetDirs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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 View Code Duplication
            if ($installed !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                if ($installed !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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... ";
0 ignored issues
show
Bug introduced by
The variable $standardName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
573
                    if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
574
                        echo PHP_EOL;
575
                    }
576
                }
577
578
                $sniffs = array_merge($sniffs, $this->processRuleset($standard));
0 ignored issues
show
Security Bug introduced by
It seems like $standard can also be of type false; however, PHP_CodeSniffer::processRuleset() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
579
            }//end foreach
580
        }//end if
581
582
        $sniffRestrictions = array();
583 View Code Duplication
        foreach ($restrictions as $sniffCode) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
584
            $parts = explode('.', strtolower($sniffCode));
585
            $sniffRestrictions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff';
586
        }
587
588
        $sniffExclusions = array();
589 View Code Duplication
        foreach ($exclusions as $sniffCode) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Documentation introduced by
$files is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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 View Code Duplication
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $currentDir does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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 View Code Duplication
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
        if (substr($ref, 0, 9) === 'Internal.') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
        if (substr($ref, 0, 1) === '.') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
        if (substr($ref, 0, 2) === '~/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
It seems like $path defined by $this->getInstalledStandardPath($ref) on line 1065 can also be of type false or null; however, PHP_CodeSniffer::isPharFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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
0 ignored issues
show
Security Bug introduced by
It seems like $stdPath defined by $this->getInstalledStandardPath($stdName) on line 1103 can also be of type false; however, PHP_CodeSniffer::isPharFile() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression self::$rulesetDirs of type string is not traversable.
Loading history...
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));
0 ignored issues
show
Security Bug introduced by
It seems like $ref defined by $path on line 1076 can also be of type false; however, PHP_CodeSniffer::_expandSniffDirectory() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
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));
0 ignored issues
show
Security Bug introduced by
It seems like $ref defined by $path on line 1076 can also be of type false; however, PHP_CodeSniffer::processRuleset() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
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 View Code Duplication
        if (isset($rule->severity) === true
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
        if (isset($rule->type) === true
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
        if (isset($rule->message) === true
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)
0 ignored issues
show
Unused Code introduced by
The parameter $depth is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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 View Code Duplication
        if (PHP_CODESNIFFER_CBF === true
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1337
            && isset($element['phpcbf-only']) === true
1338
            && (string) $element['phpcbf-only'] === 'true'
1339
        ) {
1340
            return true;
1341
        }
1342
1343 View Code Duplication
        if (PHP_CODESNIFFER_CBF === false
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
            if (empty($restrictions) === false
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
            if (empty($exclusions) === false
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression $paths of type string is not traversable.
Loading history...
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;
0 ignored issues
show
Unused Code introduced by
$firstContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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);
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);
0 ignored issues
show
Coding Style Compatibility introduced by
The method processFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1843
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $startTime does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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).
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
setConfigData uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The variable $configFile does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Unknown type name "" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2457
     * @see    getConfigData()
2458
     */
2459
    public static function getAllConfigData()
0 ignored issues
show
Coding Style introduced by
getAllConfigData uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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;
0 ignored issues
show
Bug introduced by
The variable $phpCodeSnifferConfig does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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