Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * PHP_CodeSniffer tokenises PHP code and detects violations of a
4
 * defined set of coding standards.
5
 *
6
 * PHP version 5
7
 *
8
 * @category  PHP
9
 * @package   PHP_CodeSniffer
10
 * @author    Greg Sherwood <gsherwood@squiz.net>
11
 * @author    Marc McIntyre <mmcintyre@squiz.net>
12
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
13
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
14
 * @version   CVS: $Id: CodeSniffer.php 290802 2009-11-16 06:06:29Z squiz $
15
 * @link      http://pear.php.net/package/PHP_CodeSniffer
16
 */
17
 
18
spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
19
 
20
if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
21
    throw new Exception('Class PHP_CodeSniffer_Exception not found');
22
}
23
 
24
if (class_exists('PHP_CodeSniffer_File', true) === false) {
25
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
26
}
27
 
28
if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
29
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
30
}
31
 
32
if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) {
33
    throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found');
34
}
35
 
36
if (interface_exists('PHP_CodeSniffer_MultiFileSniff', true) === false) {
37
    throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_MultiFileSniff not found');
38
}
39
 
40
/**
41
 * PHP_CodeSniffer tokenises PHP code and detects violations of a
42
 * defined set of coding standards.
43
 *
44
 * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff
45
 * interface. A sniff registers what token types it wishes to listen for, then
46
 * PHP_CodeSniffer encounters that token, the sniff is invoked and passed
47
 * information about where the token was found in the stack, and the token stack
48
 * itself.
49
 *
50
 * Sniff files and their containing class must be prefixed with Sniff, and
51
 * have an extension of .php.
52
 *
53
 * Multiple PHP_CodeSniffer operations can be performed by re-calling the
54
 * process function with different parameters.
55
 *
56
 * @category  PHP
57
 * @package   PHP_CodeSniffer
58
 * @author    Greg Sherwood <gsherwood@squiz.net>
59
 * @author    Marc McIntyre <mmcintyre@squiz.net>
60
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
61
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
62
 * @version   Release: 1.2.1
63
 * @link      http://pear.php.net/package/PHP_CodeSniffer
64
 */
65
class PHP_CodeSniffer
66
{
67
 
68
    /**
69
     * The file or directory that is currently being processed.
70
     *
71
     * @var string
72
     */
73
    protected $file = array();
74
 
75
    /**
76
     * The directory to search for sniffs in.
77
     *
78
     * @var string
79
     */
80
    protected $standardDir = '';
81
 
82
    /**
83
     * The files that have been processed.
84
     *
85
     * @var array(PHP_CodeSniffer_File)
86
     */
87
    protected $files = array();
88
 
89
    /**
90
     * The path that that PHP_CodeSniffer is being run from.
91
     *
92
     * Stored so that the path can be restored after it is changed
93
     * in the constructor.
94
     *
95
     * @var string
96
     */
97
    private $_cwd = null;
98
 
99
    /**
100
     * The listeners array.
101
     *
102
     * @var array(PHP_CodeSniffer_Sniff)
103
     */
104
    protected $listeners = array();
105
 
106
    /**
107
     * The listeners array, indexed by token type.
108
     *
109
     * @var array()
110
     */
111
    private $_tokenListeners = array(
112
                                'file'      => array(),
113
                                'multifile' => array(),
114
                               );
115
 
116
    /**
117
     * An array of patterns to use for skipping files.
118
     *
119
     * @var array()
120
     */
121
    protected $ignorePatterns = array();
122
 
123
    /**
124
     * An array of extensions for files we will check.
125
     *
126
     * @var array
127
     */
128
    public $allowedFileExtensions = array(
129
                                     'php' => 'PHP',
130
                                     'inc' => 'PHP',
131
                                     'js'  => 'JS',
132
                                     'css' => 'CSS',
133
                                    );
134
 
135
    /**
136
     * An array of variable types for param/var we will check.
137
     *
138
     * @var array(string)
139
     */
140
    public static $allowedTypes = array(
141
                                   'array',
142
                                   'boolean',
143
                                   'float',
144
                                   'integer',
145
                                   'mixed',
146
                                   'object',
147
                                   'string',
148
                                  );
149
 
150
 
151
    /**
152
     * Constructs a PHP_CodeSniffer object.
153
     *
154
     * @param int $verbosity The verbosity level.
155
     *                       1: Print progress information.
156
     *                       2: Print developer debug information.
157
     * @param int $tabWidth  The number of spaces each tab represents.
158
     *                       If greater than zero, tabs will be replaced
159
     *                       by spaces before testing each file.
160
     *
161
     * @see process()
162
     */
163
    public function __construct($verbosity=0, $tabWidth=0)
164
    {
165
        if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
166
            define('PHP_CODESNIFFER_VERBOSITY', $verbosity);
167
        }
168
 
169
        if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) {
170
            define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth);
171
        }
172
 
173
        // Change into a directory that we know about to stop any
174
        // relative path conflicts.
175
        $this->_cwd = getcwd();
176
        chdir(dirname(__FILE__).'/CodeSniffer/');
177
 
178
    }//end __construct()
179
 
180
 
181
    /**
182
     * Destructs a PHP_CodeSniffer object.
183
     *
184
     * Restores the current working directory to what it
185
     * was before we started our run.
186
     *
187
     * @return void
188
     */
189
    public function __destruct()
190
    {
191
        chdir($this->_cwd);
192
 
193
    }//end __destruct()
194
 
195
 
196
    /**
197
     * Autoload static method for loading classes and interfaces.
198
     *
199
     * @param string $className The name of the class or interface.
200
     *
201
     * @return void
202
     */
203
    public static function autoload($className)
204
    {
205
        if (substr($className, 0, 4) === 'PHP_') {
206
            $newClassName = substr($className, 4);
207
        } else {
208
            $newClassName = $className;
209
        }
210
 
211
        $path = str_replace('_', '/', $newClassName).'.php';
212
 
213
        if (is_file(dirname(__FILE__).'/'.$path) === true) {
214
            // Check standard file locations based on class name.
215
            include dirname(__FILE__).'/'.$path;
216
        } else if (is_file(dirname(__FILE__).'/CodeSniffer/Standards/'.$path) === true) {
217
            // Check for included sniffs.
218
            include dirname(__FILE__).'/CodeSniffer/Standards/'.$path;
219
        } else {
220
            // Everything else.
221
            @include $path;
222
        }
223
 
224
    }//end autoload()
225
 
226
 
227
    /**
228
     * Sets an array of file extensions that we will allow checking of.
229
     *
230
     * If the extension is one of the defaults, a specific tokenizer
231
     * will be used. Otherwise, the PHP tokenizer will be used for
232
     * all extensions passed.
233
     *
234
     * @param array $extensions An array of file extensions.
235
     *
236
     * @return void
237
     */
238
    public function setAllowedFileExtensions(array $extensions)
239
    {
240
        $newExtensions = array();
241
        foreach ($extensions as $ext) {
242
            if (isset($this->allowedFileExtensions[$ext]) === true) {
243
                $newExtensions[$ext] = $this->allowedFileExtensions[$ext];
244
            } else {
245
                $newExtensions[$ext] = 'PHP';
246
            }
247
        }
248
 
249
        $this->allowedFileExtensions = $newExtensions;
250
 
251
    }//end setAllowedFileExtensions()
252
 
253
 
254
    /**
255
     * Sets an array of ignore patterns that we use to skip files and folders.
256
     *
257
     * Patterns are not case sensitive.
258
     *
259
     * @param array $patterns An array of ignore patterns.
260
     *
261
     * @return void
262
     */
263
    public function setIgnorePatterns(array $patterns)
264
    {
265
        $this->ignorePatterns = $patterns;
266
 
267
    }//end setIgnorePatterns()
268
 
269
 
270
    /**
271
     * Adds a file to the list of checked files.
272
     *
273
     * Checked files are used to generate error reports after the run.
274
     *
275
     * @param PHP_CodeSniffer_File $phpcsFile The file to add.
276
     *
277
     * @return void
278
     */
279
    public function addFile(PHP_CodeSniffer_File $phpcsFile)
280
    {
281
        $this->files[] = $phpcsFile;
282
 
283
    }//end addFile()
284
 
285
 
286
    /**
287
     * Processes the files/directories that PHP_CodeSniffer was constructed with.
288
     *
289
     * @param string|array $files    The files and directories to process. For
290
     *                               directories, each sub directory will also
291
     *                               be traversed for source files.
292
     * @param string       $standard The set of code sniffs we are testing
293
     *                               against.
294
     * @param array        $sniffs   The sniff names to restrict the allowed
295
     *                               listeners to.
296
     * @param boolean      $local    If true, don't recurse into directories.
297
     *
298
     * @return void
299
     * @throws PHP_CodeSniffer_Exception If files or standard are invalid.
300
     */
301
    public function process($files, $standard, array $sniffs=array(), $local=false)
302
    {
303
        if (is_array($files) === false) {
304
            if (is_string($files) === false || $files === null) {
305
                throw new PHP_CodeSniffer_Exception('$file must be a string');
306
            }
307
 
308
            $files = array($files);
309
        }
310
 
311
        if (is_string($standard) === false || $standard === null) {
312
            throw new PHP_CodeSniffer_Exception('$standard must be a string');
313
        }
314
 
315
        // Reset the members.
316
        $this->listeners       = array();
317
        $this->files           = array();
318
        $this->_tokenListeners = array(
319
                                  'file'      => array(),
320
                                  'multifile' => array(),
321
                                 );
322
 
323
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
324
            echo "Registering sniffs in ".basename($standard)." standard... ";
325
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
326
                echo PHP_EOL;
327
            }
328
        }
329
 
330
        $this->setTokenListeners($standard, $sniffs);
331
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
332
            $numSniffs = count($this->listeners);
333
            echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
334
        }
335
 
336
        $this->populateTokenListeners();
337
 
338
        foreach ($files as $file) {
339
            $this->file = $file;
340
            if (is_dir($this->file) === true) {
341
                $this->processFiles($this->file, $local);
342
            } else {
343
                $this->processFile($this->file);
344
            }
345
        }
346
 
347
        // Now process the multi-file sniffs, assuming there are
348
        // multiple files being sniffed.
349
        if (count($files) > 1 || is_dir($files[0]) === true) {
350
            foreach ($this->_tokenListeners['multifile'] as $listener) {
351
                // Set the name of the listener for error messages.
352
                $activeListener = get_class($listener);
353
                foreach ($this->files as $file) {
354
                    $file->setActiveListener($activeListener);
355
                }
356
 
357
                $listener->process($this->files);
358
            }
359
        }
360
 
361
    }//end process()
362
 
363
 
364
    /**
365
     * Gets installed sniffs in the coding standard being used.
366
     *
367
     * Traverses the standard directory for classes that implement the
368
     * PHP_CodeSniffer_Sniff interface asks them to register. Each of the
369
     * sniff's class names must be exact as the basename of the sniff file.
370
     *
371
     * Returns an array of sniff class names.
372
     *
373
     * @param string $standard The name of the coding standard we are checking.
374
     * @param array  $sniffs   The sniff names to restrict the allowed
375
     *                         listeners to.
376
     *
377
     * @return array
378
     * @throws PHP_CodeSniffer_Exception If any of the tests failed in the
379
     *                                   registration process.
380
     */
381
    public function getTokenListeners($standard, array $sniffs=array())
382
    {
383
        if (is_dir($standard) === true) {
384
            // This is an absolute path to a custom standard.
385
            $this->standardDir = $standard;
386
            $standard          = basename($standard);
387
        } else {
388
            $this->standardDir = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard);
389
            if (is_dir($this->standardDir) === false) {
390
                // This isn't looking good. Let's see if this
391
                // is a relative path to a custom standard.
392
                if (is_dir(realpath($this->_cwd.'/'.$standard)) === true) {
393
                    // This is a relative path to a custom standard.
394
                    $this->standardDir = realpath($this->_cwd.'/'.$standard);
395
                    $standard          = basename($standard);
396
                }
397
            }
398
        }
399
 
400
        $files = self::getSniffFiles($this->standardDir, $standard);
401
 
402
        if (empty($sniffs) === false) {
403
            // Convert the allowed sniffs to lower case so
404
            // they are easier to check.
405
            foreach ($sniffs as &$sniff) {
406
                $sniff = strtolower($sniff);
407
            }
408
        }
409
 
410
        $listeners = array();
411
 
412
        foreach ($files as $file) {
413
            // Work out where the position of /StandardName/Sniffs/... is
414
            // so we can determine what the class will be called.
415
            $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
416
            if ($sniffPos === false) {
417
                continue;
418
            }
419
 
420
            $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
421
            if ($slashPos === false) {
422
                continue;
423
            }
424
 
425
            $className = substr($file, ($slashPos + 1));
426
            $className = substr($className, 0, -4);
427
            $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
428
 
429
            include_once $file;
430
 
431
            // If they have specified a list of sniffs to restrict to, check
432
            // to see if this sniff is allowed.
433
            $allowed = in_array(strtolower($className), $sniffs);
434
            if (empty($sniffs) === false && $allowed === false) {
435
                continue;
436
            }
437
 
438
            $listeners[] = $className;
439
 
440
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
441
                echo "\tRegistered $className".PHP_EOL;
442
            }
443
        }//end foreach
444
 
445
        return $listeners;
446
 
447
    }//end getTokenListeners()
448
 
449
 
450
    /**
451
     * Sets installed sniffs in the coding standard being used.
452
     *
453
     * @param string $standard The name of the coding standard we are checking.
454
     * @param array  $sniffs   The sniff names to restrict the allowed
455
     *                         listeners to.
456
     *
457
     * @return null
458
     */
459
    public function setTokenListeners($standard, array $sniffs=array())
460
    {
461
        $this->listeners = $this->getTokenListeners($standard, $sniffs);
462
 
463
    }//end setTokenListeners()
464
 
465
 
466
    /**
467
     * Populates the array of PHP_CodeSniffer_Sniff's for this file.
468
     *
469
     * @return void
470
     */
471
    public function populateTokenListeners()
472
    {
473
        // Construct a list of listeners indexed by token being listened for.
474
        foreach ($this->listeners as $listenerClass) {
475
            $listener = new $listenerClass();
476
 
477
            if (($listener instanceof PHP_CodeSniffer_Sniff) === true) {
478
                $tokens = $listener->register();
479
                if (is_array($tokens) === false) {
480
                    $msg = "Sniff $listenerClass register() method must return an array";
481
                    throw new PHP_CodeSniffer_Exception($msg);
482
                }
483
 
484
                foreach ($tokens as $token) {
485
                    if (isset($this->_tokenListeners['file'][$token]) === false) {
486
                        $this->_tokenListeners['file'][$token] = array();
487
                    }
488
 
489
                    if (in_array($listener, $this->_tokenListeners['file'][$token], true) === false) {
490
                        $this->_tokenListeners['file'][$token][] = $listener;
491
                    }
492
                }
493
            } else if (($listener instanceof PHP_CodeSniffer_MultiFileSniff) === true) {
494
                $this->_tokenListeners['multifile'][] = $listener;
495
            }
496
        }//end foreach
497
 
498
    }//end populateTokenListeners()
499
 
500
 
501
    /**
502
     * Return a list of sniffs that a coding standard has defined.
503
     *
504
     * Sniffs are found by recursing the standard directory and also by
505
     * asking the standard for included sniffs.
506
     *
507
     * @param string $dir      The directory where to look for the files.
508
     * @param string $standard The name of the coding standard. If NULL, no
509
     *                         included sniffs will be checked for.
510
     *
511
     * @return array
512
     * @throws PHP_CodeSniffer_Exception If an included or excluded sniff does
513
     *                                   not exist.
514
     */
515
    public static function getSniffFiles($dir, $standard=null)
516
    {
517
        $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
518
 
519
        $ownSniffs      = array();
520
        $includedSniffs = array();
521
        $excludedSniffs = array();
522
 
523
        foreach ($di as $file) {
524
            $fileName = $file->getFilename();
525
 
526
            // Skip hidden files.
527
            if (substr($fileName, 0, 1) === '.') {
528
                continue;
529
            }
530
 
531
            // We are only interested in PHP and sniff files.
532
            $fileParts = explode('.', $fileName);
533
            if (array_pop($fileParts) !== 'php') {
534
                continue;
535
            }
536
 
537
            $basename = basename($fileName, '.php');
538
            if (substr($basename, -5) !== 'Sniff') {
539
                continue;
540
            }
541
 
542
            $ownSniffs[] = $file->getPathname();
543
        }//end foreach
544
 
545
        // Load the standard class and ask it for a list of external
546
        // sniffs to include in the standard.
547
        if ($standard !== null
548
            && is_file("$dir/{$standard}CodingStandard.php") === true
549
        ) {
550
            include_once "$dir/{$standard}CodingStandard.php";
551
            $standardClassName = "PHP_CodeSniffer_Standards_{$standard}_{$standard}CodingStandard";
552
            $standardClass     = new $standardClassName;
553
 
554
            $included = $standardClass->getIncludedSniffs();
555
            foreach ($included as $sniff) {
556
                if (is_dir($sniff) === true) {
557
                    // Trying to include from a custom standard.
558
                    $sniffDir = $sniff;
559
                    $sniff    = basename($sniff);
560
                } else if (is_file($sniff) === true) {
561
                    // Trying to include a custom sniff.
562
                    $sniffDir = $sniff;
563
                } else {
564
                    $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff");
565
                    if ($sniffDir === false) {
566
                        $error = "Included sniff $sniff does not exist";
567
                        throw new PHP_CodeSniffer_Exception($error);
568
                    }
569
                }
570
 
571
                if (is_dir($sniffDir) === true) {
572
                    if (self::isInstalledStandard($sniff) === true) {
573
                        // We are including a whole coding standard.
574
                        $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir, $sniff));
575
                    } else {
576
                        // We are including a whole directory of sniffs.
577
                        $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir));
578
                    }
579
                } else {
580
                    if (substr($sniffDir, -9) !== 'Sniff.php') {
581
                        $error = "Included sniff $sniff does not exist";
582
                        throw new PHP_CodeSniffer_Exception($error);
583
                    }
584
 
585
                    $includedSniffs[] = $sniffDir;
586
                }
587
            }//end foreach
588
 
589
            $excluded = $standardClass->getExcludedSniffs();
590
            foreach ($excluded as $sniff) {
591
                if (is_dir($sniff) === true) {
592
                    // Trying to exclude from a custom standard.
593
                    $sniffDir = $sniff;
594
                    $sniff    = basename($sniff);
595
                } else if (is_file($sniff) === true) {
596
                    // Trying to exclude a custom sniff.
597
                    $sniffDir = $sniff;
598
                } else {
599
                    $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff");
600
                    if ($sniffDir === false) {
601
                        $error = "Excluded sniff $sniff does not exist";
602
                        throw new PHP_CodeSniffer_Exception($error);
603
                    }
604
                }
605
 
606
                if (is_dir($sniffDir) === true) {
607
                    if (self::isInstalledStandard($sniff) === true) {
608
                        // We are excluding a whole coding standard.
609
                        $excludedSniffs = array_merge(
610
                            $excludedSniffs,
611
                            self::getSniffFiles($sniffDir, $sniff)
612
                        );
613
                    } else {
614
                        // We are excluding a whole directory of sniffs.
615
                        $excludedSniffs = array_merge(
616
                            $excludedSniffs,
617
                            self::getSniffFiles($sniffDir)
618
                        );
619
                    }
620
                } else {
621
                    if (substr($sniffDir, -9) !== 'Sniff.php') {
622
                        $error = "Excluded sniff $sniff does not exist";
623
                        throw new PHP_CodeSniffer_Exception($error);
624
                    }
625
 
626
                    $excludedSniffs[] = $sniffDir;
627
                }
628
            }//end foreach
629
        }//end if
630
 
631
        // Merge our own sniff list with our exnternally included
632
        // sniff list, but filter out any excluded sniffs.
633
        $files = array();
634
        foreach (array_merge($ownSniffs, $includedSniffs) as $sniff) {
635
            if (in_array($sniff, $excludedSniffs) === true) {
636
                continue;
637
            } else {
638
                $files[] = $sniff;
639
            }
640
        }
641
 
642
        return $files;
643
 
644
    }//end getSniffFiles()
645
 
646
 
647
    /**
648
     * Run the code sniffs over each file in a given directory.
649
     *
650
     * Recusively reads the specified directory and performs the PHP_CodeSniffer
651
     * sniffs on each source file found within the directories.
652
     *
653
     * @param string  $dir   The directory to process.
654
     * @param boolean $local If true, only process files in this directory, not
655
     *                       sub directories.
656
     *
657
     * @return void
658
     * @throws Exception If there was an error opening the directory.
659
     */
660
    public function processFiles($dir, $local=false)
661
    {
662
        try {
663
            if ($local === true) {
664
                $di = new DirectoryIterator($dir);
665
            } else {
666
                $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
667
            }
668
 
669
            foreach ($di as $file) {
670
                $filePath = realpath($file->getPathname());
671
 
672
                if (is_dir($filePath) === true) {
673
                    continue;
674
                }
675
 
676
                // Check that the file's extension is one we are checking.
677
                // Note that because we are doing a whole directory, we
678
                // are strict about checking the extension and we don't
679
                // let files with no extension through.
680
                $fileParts = explode('.', $file);
681
                $extension = array_pop($fileParts);
682
                if ($extension === $file) {
683
                    continue;
684
                }
685
 
686
                if (isset($this->allowedFileExtensions[$extension]) === false) {
687
                    continue;
688
                }
689
 
690
                $this->processFile($filePath);
691
            }//end foreach
692
        } catch (Exception $e) {
693
            $trace = $e->getTrace();
694
 
695
            $filename = $trace[0]['args'][0];
696
            if (is_numeric($filename) === true) {
697
                // See if we can find the PHP_CodeSniffer_File object.
698
                foreach ($trace as $data) {
699
                    if (isset($data['args'][0]) === true && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true) {
700
                        $filename = $data['args'][0]->getFilename();
701
                    }
702
                }
703
            }
704
 
705
            $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
706
 
707
            $phpcsFile = new PHP_CodeSniffer_File($filename, $this->listeners, $this->allowedFileExtensions);
708
            $this->addFile($phpcsFile);
709
            $phpcsFile->addError($error, null);
710
            return;
711
        }
712
 
713
    }//end processFiles()
714
 
715
 
716
    /**
717
     * Run the code sniffs over a single given file.
718
     *
719
     * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it
720
     * conforms with the standard.
721
     *
722
     * @param string $file     The file to process.
723
     * @param string $contents The contents to parse. If NULL, the content
724
     *                         is taken from the file system.
725
     *
726
     * @return void
727
     * @throws PHP_CodeSniffer_Exception If the file could not be processed.
728
     */
729
    public function processFile($file, $contents=null)
730
    {
731
        if ($contents === null && file_exists($file) === false) {
732
            throw new PHP_CodeSniffer_Exception("Source file $file does not exist");
733
        }
734
 
735
        // If the file's path matches one of our ignore patterns, skip it.
736
        foreach ($this->ignorePatterns as $pattern) {
737
            $replacements = array(
738
                             '\\,' => ',',
739
                             '*'   => '.*',
740
                            );
741
 
742
            $pattern = strtr($pattern, $replacements);
743
            if (preg_match("|{$pattern}|i", $file) === 1) {
744
                return;
745
            }
746
        }
747
 
748
        // Before we go and spend time tokenizing this file, just check
749
        // to see if there is a tag up top to indicate that the whole
750
        // file should be ignored. It must be on one of the first two lines.
751
        $firstContent = $contents;
752
        if ($contents === null) {
753
            $handle = fopen($file, 'r');
754
            if ($handle !== false) {
755
                $firstContent  = fgets($handle);
756
                $firstContent .= fgets($handle);
757
                fclose($handle);
758
            }
759
        }
760
 
761
        if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) {
762
            // We are ignoring the whole file.
763
            if (PHP_CODESNIFFER_VERBOSITY > 0) {
764
                echo 'Ignoring '.basename($file).PHP_EOL;
765
            }
766
 
767
            return;
768
        }
769
 
770
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
771
            $startTime = time();
772
            echo 'Processing '.basename($file).' ';
773
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
774
                echo PHP_EOL;
775
            }
776
        }
777
 
778
        $phpcsFile = new PHP_CodeSniffer_File(
779
            $file,
780
            $this->_tokenListeners['file'],
781
            $this->allowedFileExtensions
782
        );
783
        $this->addFile($phpcsFile);
784
        $phpcsFile->start($contents);
785
 
786
        // Clean up the test if we can to save memory. This can't be done if
787
        // we need to leave the files around for multi-file sniffs.
788
        if (empty($this->_tokenListeners['multifile']) === true) {
789
            $phpcsFile->cleanUp();
790
        }
791
 
792
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
793
            $timeTaken = (time() - $startTime);
794
            if ($timeTaken === 0) {
795
                echo 'DONE in < 1 second';
796
            } else if ($timeTaken === 1) {
797
                echo 'DONE in 1 second';
798
            } else {
799
                echo "DONE in $timeTaken seconds";
800
            }
801
 
802
            $errors   = $phpcsFile->getErrorCount();
803
            $warnings = $phpcsFile->getWarningCount();
804
            echo " ($errors errors, $warnings warnings)".PHP_EOL;
805
        }
806
 
807
    }//end processFile()
808
 
809
 
810
    /**
811
     * Pre-process and package errors and warnings for all files.
812
     *
813
     * Used by error reports to get a packaged list of all errors and
814
     * warnings in each file.
815
     *
816
     * @param boolean $showWarnings Show warnings as well as errors.
817
     *
818
     * @return array
819
     */
820
    public function prepareErrorReport($showWarnings=true)
821
    {
822
        $report = array(
823
                   'totals' => array(
824
                                'warnings' => 0,
825
                                'errors'   => 0,
826
                               ),
827
                   'files'  => array(),
828
                  );
829
 
830
        foreach ($this->files as $file) {
831
            $warnings    = $file->getWarnings();
832
            $errors      = $file->getErrors();
833
            $numWarnings = $file->getWarningCount();
834
            $numErrors   = $file->getErrorCount();
835
            $filename    = $file->getFilename();
836
 
837
            $report['files'][$filename] = array(
838
                                           'errors'   => 0,
839
                                           'warnings' => 0,
840
                                           'messages' => array(),
841
                                          );
842
 
843
            if ($numErrors === 0 && $numWarnings === 0) {
844
                // Prefect score!
845
                continue;
846
            }
847
 
848
            if ($numErrors === 0 && $showWarnings === false) {
849
                // Prefect score (sort of).
850
                continue;
851
            }
852
 
853
            $report['files'][$filename]['errors'] = $numErrors;
854
            if ($showWarnings === true) {
855
                $report['files'][$filename]['warnings'] = $numWarnings;
856
            } else {
857
                $report['files'][$filename]['warnings'] = 0;
858
            }
859
 
860
            $report['totals']['errors'] += $numErrors;
861
            if ($showWarnings === true) {
862
                $report['totals']['warnings'] += $numWarnings;
863
            }
864
 
865
            // Merge errors and warnings.
866
            foreach ($errors as $line => $lineErrors) {
867
                foreach ($lineErrors as $column => $colErrors) {
868
                    $newErrors = array();
869
                    foreach ($colErrors as $data) {
870
                        $newErrors[] = array(
871
                                        'message' => $data['message'],
872
                                        'source'  => $data['source'],
873
                                        'type'    => 'ERROR',
874
                                       );
875
                    }
876
 
877
                    $errors[$line][$column] = $newErrors;
878
                }
879
            }//end foreach
880
 
881
            if ($showWarnings === true) {
882
                foreach ($warnings as $line => $lineWarnings) {
883
                    foreach ($lineWarnings as $column => $colWarnings) {
884
                        $newWarnings = array();
885
                        foreach ($colWarnings as $data) {
886
                            $newWarnings[] = array(
887
                                              'message' => $data['message'],
888
                                              'source'  => $data['source'],
889
                                              'type'    => 'WARNING',
890
                                             );
891
                        }
892
 
893
                        if (isset($errors[$line]) === false) {
894
                            $errors[$line] = array();
895
                        }
896
 
897
                        if (isset($errors[$line][$column]) === true) {
898
                            $errors[$line][$column] = array_merge(
899
                                $newWarnings,
900
                                $errors[$line][$column]
901
                            );
902
                        } else {
903
                            $errors[$line][$column] = $newWarnings;
904
                        }
905
                    }
906
                }//end foreach
907
            }//end if
908
 
909
            ksort($errors);
910
 
911
            $report['files'][$filename]['messages'] = $errors;
912
        }//end foreach
913
 
914
        ksort($report['files']);
915
 
916
        return $report;
917
 
918
    }//end prepareErrorReport()
919
 
920
 
921
    /**
922
     * Prints all errors and warnings for each file processed, in an XML format.
923
     *
924
     * Errors and warnings are displayed together, grouped by file.
925
     *
926
     * @param boolean $showWarnings Show warnings as well as errors.
927
     *
928
     * @return int The number of error and warning messages shown.
929
     */
930
    public function printXMLErrorReport($showWarnings=true)
931
    {
932
        echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
933
        echo '<phpcs version="1.2.1">'.PHP_EOL;
934
 
935
        $errorsShown = 0;
936
 
937
        $report = $this->prepareErrorReport($showWarnings);
938
        foreach ($report['files'] as $filename => $file) {
939
            if (empty($file['messages']) === true) {
940
                continue;
941
            }
942
 
943
            echo ' <file name="'.$filename.'" errors="'.$file['errors'].'" warnings="'.$file['warnings'].'">'.PHP_EOL;
944
 
945
            foreach ($file['messages'] as $line => $lineErrors) {
946
                foreach ($lineErrors as $column => $colErrors) {
947
                    foreach ($colErrors as $error) {
948
                        $error['type'] = strtolower($error['type']);
949
                        echo '  <'.$error['type'].' line="'.$line.'" column="'.$column.'" source="'.$error['source'].'">';
950
                        echo htmlspecialchars($error['message']).'</'.$error['type'].'>'.PHP_EOL;
951
                        $errorsShown++;
952
                    }
953
                }
954
            }//end foreach
955
 
956
            echo ' </file>'.PHP_EOL;
957
        }//end foreach
958
 
959
        echo '</phpcs>'.PHP_EOL;
960
 
961
        return $errorsShown;
962
 
963
    }//end printXMLErrorReport()
964
 
965
 
966
    /**
967
     * Prints all errors and warnings for processed files, in a Checkstyle format.
968
     *
969
     * Errors and warnings are displayed together, grouped by file.
970
     *
971
     * @param boolean $showWarnings Show warnings as well as errors.
972
     *
973
     * @return int The number of error and warning messages shown.
974
     */
975
    public function printCheckstyleErrorReport($showWarnings=true)
976
    {
977
        echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
978
        echo '<checkstyle version="1.2.1">'.PHP_EOL;
979
 
980
        $errorsShown = 0;
981
 
982
        $report = $this->prepareErrorReport($showWarnings);
983
        foreach ($report['files'] as $filename => $file) {
984
            echo ' <file name="'.$filename.'">'.PHP_EOL;
985
 
986
            foreach ($file['messages'] as $line => $lineErrors) {
987
                foreach ($lineErrors as $column => $colErrors) {
988
                    foreach ($colErrors as $error) {
989
                        $error['type'] = strtolower($error['type']);
990
                        echo '  <error';
991
                        echo ' line="'.$line.'" column="'.$column.'"';
992
                        echo ' severity="'.$error['type'].'"';
993
                        $message = utf8_encode(htmlspecialchars($error['message']));
994
                        echo ' message="'.$message.'"';
995
                        echo ' source="'.$error['source'].'"';
996
                        echo '/>'.PHP_EOL;
997
                        $errorsShown++;
998
                    }
999
                }
1000
            }//end foreach
1001
 
1002
            echo ' </file>'.PHP_EOL;
1003
        }//end foreach
1004
 
1005
        echo '</checkstyle>'.PHP_EOL;
1006
 
1007
        return $errorsShown;
1008
 
1009
    }//end printCheckstyleErrorReport()
1010
 
1011
 
1012
    /**
1013
     * Prints all errors and warnings for each file processed, in a CSV format.
1014
     *
1015
     * @param boolean $showWarnings Show warnings as well as errors.
1016
     *
1017
     * @return int The number of error and warning messages shown.
1018
     */
1019
    public function printCSVErrorReport($showWarnings=true)
1020
    {
1021
        echo 'File,Line,Column,Severity,Message,Source'.PHP_EOL;
1022
 
1023
        $errorsShown = 0;
1024
 
1025
        $report = $this->prepareErrorReport($showWarnings);
1026
        foreach ($report['files'] as $filename => $file) {
1027
            foreach ($file['messages'] as $line => $lineErrors) {
1028
                foreach ($lineErrors as $column => $colErrors) {
1029
                    foreach ($colErrors as $error) {
1030
                        $filename = str_replace('"', '\"', $filename);
1031
                        $message  = str_replace('"', '\"', $error['message']);
1032
                        $type     = strtolower($error['type']);
1033
                        $source   = $error['source'];
1034
                        echo "\"$filename\",$line,$column,$type,\"$message\",$source".PHP_EOL;
1035
                        $errorsShown++;
1036
                    }
1037
                }
1038
            }//end foreach
1039
        }//end foreach
1040
 
1041
        return $errorsShown;
1042
 
1043
    }//end printCSVErrorReport()
1044
 
1045
 
1046
    /**
1047
     * Prints all errors and warnings for each file processed, in a format for emacs.
1048
     *
1049
     * @param boolean $showWarnings Show warnings as well as errors.
1050
     *
1051
     * @return int The number of error and warning messages shown.
1052
     */
1053
    public function printEmacsErrorReport($showWarnings=true)
1054
    {
1055
        $errorsShown = 0;
1056
 
1057
        $report = $this->prepareErrorReport($showWarnings);
1058
        foreach ($report['files'] as $filename => $file) {
1059
            foreach ($file['messages'] as $line => $lineErrors) {
1060
                foreach ($lineErrors as $column => $colErrors) {
1061
                    foreach ($colErrors as $error) {
1062
                        $message = $error['message'];
1063
                        $type    = strtolower($error['type']);
1064
                        echo "$filename:$line:$column: $type - $message".PHP_EOL;
1065
                        $errorsShown++;
1066
                    }
1067
                }
1068
            }//end foreach
1069
        }//end foreach
1070
 
1071
        return $errorsShown;
1072
 
1073
    }//end printEmacsErrorReport()
1074
 
1075
 
1076
    /**
1077
     * Prints all errors and warnings for each file processed.
1078
     *
1079
     * Errors and warnings are displayed together, grouped by file.
1080
     *
1081
     * @param boolean $showWarnings Show warnings as well as errors.
1082
     * @param boolean $showSources  Show error sources in report.
1083
     * @param int     $width        How wide the report should be.
1084
     *
1085
     * @return int The number of error and warning messages shown.
1086
     */
1087
    public function printErrorReport($showWarnings=true, $showSources=false, $width=80)
1088
    {
1089
        $errorsShown = 0;
1090
        $width       = max($width, 70);
1091
 
1092
        $report = $this->prepareErrorReport($showWarnings);
1093
        foreach ($report['files'] as $filename => $file) {
1094
            if (empty($file['messages']) === true) {
1095
                continue;
1096
            }
1097
 
1098
            echo PHP_EOL.'FILE: ';
1099
            if (strlen($filename) <= ($width - 9)) {
1100
                echo $filename;
1101
            } else {
1102
                echo '...'.substr($filename, (strlen($filename) - $width - 9));
1103
            }
1104
 
1105
            echo PHP_EOL;
1106
            echo str_repeat('-', $width).PHP_EOL;
1107
 
1108
            echo 'FOUND '.$file['errors'].' ERROR(S) ';
1109
 
1110
            if ($showWarnings === true) {
1111
                echo 'AND '.$file['warnings'].' WARNING(S) ';
1112
            }
1113
 
1114
            echo 'AFFECTING '.count($file['messages']).' LINE(S)'.PHP_EOL;
1115
            echo str_repeat('-', $width).PHP_EOL;
1116
 
1117
            // Work out the max line number for formatting.
1118
            $maxLine = 0;
1119
            foreach ($file['messages'] as $line => $lineErrors) {
1120
                if ($line > $maxLine) {
1121
                    $maxLine = $line;
1122
                }
1123
            }
1124
 
1125
            $maxLineLength = strlen($maxLine);
1126
 
1127
            // The length of the word ERROR or WARNING; used for padding.
1128
            if ($showWarnings === true && $file['warnings'] > 0) {
1129
                $typeLength = 7;
1130
            } else {
1131
                $typeLength = 5;
1132
            }
1133
 
1134
            // The padding that all lines will require that are
1135
            // printing an error message overflow.
1136
            $paddingLine2  = str_repeat(' ', ($maxLineLength + 1));
1137
            $paddingLine2 .= ' | ';
1138
            $paddingLine2 .= str_repeat(' ', $typeLength);
1139
            $paddingLine2 .= ' | ';
1140
 
1141
            // The maxium amount of space an error message can use.
1142
            $maxErrorSpace = ($width - strlen($paddingLine2) - 1);
1143
 
1144
            foreach ($file['messages'] as $line => $lineErrors) {
1145
                foreach ($lineErrors as $column => $colErrors) {
1146
                    foreach ($colErrors as $error) {
1147
                        $message = $error['message'];
1148
                        if ($showSources === true) {
1149
                            $message .= ' ('.substr($error['source'], 0, -5).')';
1150
                        }
1151
 
1152
                        // The padding that goes on the front of the line.
1153
                        $padding  = ($maxLineLength - strlen($line));
1154
                        $errorMsg = wordwrap(
1155
                            $message,
1156
                            $maxErrorSpace,
1157
                            PHP_EOL."$paddingLine2"
1158
                        );
1159
 
1160
                        echo ' '.str_repeat(' ', $padding).$line.' | '.$error['type'];
1161
                        if ($error['type'] === 'ERROR') {
1162
                            if ($showWarnings === true && $file['warnings'] > 0) {
1163
                                echo '  ';
1164
                            }
1165
                        }
1166
 
1167
                        echo ' | '.$errorMsg.PHP_EOL;
1168
                        $errorsShown++;
1169
                    }//end foreach
1170
                }//end foreach
1171
            }//end foreach
1172
 
1173
            echo str_repeat('-', $width).PHP_EOL.PHP_EOL;
1174
        }//end foreach
1175
 
1176
        return $errorsShown;
1177
 
1178
    }//end printErrorReport()
1179
 
1180
 
1181
    /**
1182
     * Prints a summary of errors and warnings for each file processed.
1183
     *
1184
     * If verbose output is enabled, results are shown for all files, even if
1185
     * they have no errors or warnings. If verbose output is disabled, we only
1186
     * show files that have at least one warning or error.
1187
     *
1188
     * @param boolean $showWarnings Show warnings as well as errors.
1189
     * @param boolean $showSources  Show error sources in report.
1190
     * @param int     $width        How wide the report should be.
1191
     *
1192
     * @return int The number of error and warning messages shown.
1193
     */
1194
    public function printErrorReportSummary($showWarnings=true, $showSources=false, $width=80)
1195
    {
1196
        $errorFiles = array();
1197
        $width      = max($width, 70);
1198
 
1199
        $report = $this->prepareErrorReport($showWarnings);
1200
        foreach ($report['files'] as $filename => $file) {
1201
            $numWarnings = $file['warnings'];
1202
            $numErrors   = $file['errors'];
1203
 
1204
            // If verbose output is enabled, we show the results for all files,
1205
            // but if not, we only show files that had errors or warnings.
1206
            if (PHP_CODESNIFFER_VERBOSITY > 0
1207
                || $numErrors > 0
1208
                || ($numWarnings > 0
1209
                && $showWarnings === true)
1210
            ) {
1211
                $errorFiles[$filename] = array(
1212
                                          'warnings' => $numWarnings,
1213
                                          'errors'   => $numErrors,
1214
                                         );
1215
            }//end if
1216
        }//end foreach
1217
 
1218
        if (empty($errorFiles) === true) {
1219
            // Nothing to print.
1220
            return 0;
1221
        }
1222
 
1223
        echo PHP_EOL.'PHP CODE SNIFFER REPORT SUMMARY'.PHP_EOL;
1224
        echo str_repeat('-', $width).PHP_EOL;
1225
        if ($showWarnings === true) {
1226
            echo 'FILE'.str_repeat(' ', ($width - 20)).'ERRORS  WARNINGS'.PHP_EOL;
1227
        } else {
1228
            echo 'FILE'.str_repeat(' ', ($width - 10)).'ERRORS'.PHP_EOL;
1229
        }
1230
 
1231
        echo str_repeat('-', $width).PHP_EOL;
1232
 
1233
        $totalErrors   = 0;
1234
        $totalWarnings = 0;
1235
        $totalFiles    = 0;
1236
 
1237
        foreach ($errorFiles as $file => $errors) {
1238
            if ($showWarnings === true) {
1239
                $padding = ($width - 18 - strlen($file));
1240
            } else {
1241
                $padding = ($width - 8 - strlen($file));
1242
            }
1243
 
1244
            if ($padding < 0) {
1245
                $file    = '...'.substr($file, (($padding * -1) + 3));
1246
                $padding = 0;
1247
            }
1248
 
1249
            echo $file.str_repeat(' ', $padding).'  ';
1250
            echo $errors['errors'];
1251
            if ($showWarnings === true) {
1252
                echo str_repeat(' ', (8 - strlen((string) $errors['errors'])));
1253
                echo $errors['warnings'];
1254
            }
1255
 
1256
            echo PHP_EOL;
1257
 
1258
            $totalErrors   += $errors['errors'];
1259
            $totalWarnings += $errors['warnings'];
1260
            $totalFiles++;
1261
        }//end foreach
1262
 
1263
        echo str_repeat('-', $width).PHP_EOL;
1264
        echo "A TOTAL OF $totalErrors ERROR(S) ";
1265
        if ($showWarnings === true) {
1266
            echo "AND $totalWarnings WARNING(S) ";
1267
        }
1268
 
1269
        echo "WERE FOUND IN $totalFiles FILE(S)".PHP_EOL;
1270
        echo str_repeat('-', $width).PHP_EOL.PHP_EOL;
1271
 
1272
        if ($showSources === true) {
1273
            $this->printSourceReport($showWarnings, true);
1274
        }
1275
 
1276
        return ($totalErrors + $totalWarnings);
1277
 
1278
    }//end printErrorReportSummary()
1279
 
1280
 
1281
    /**
1282
     * Prints the source of all errors and warnings.
1283
     *
1284
     * @param boolean $showWarnings Show warnings as well as errors.
1285
     * @param boolean $showSources  Show error sources in report.
1286
     * @param int     $width        How wide the report should be.
1287
     *
1288
     * @return int The number of error and warning messages shown.
1289
     */
1290
    public function printSourceReport($showWarnings=true, $showSources=false, $width=80)
1291
    {
1292
        $sources = array();
1293
        $width   = max($width, 70);
1294
 
1295
        $errorsShown = 0;
1296
 
1297
        $report = $this->prepareErrorReport($showWarnings);
1298
        foreach ($report['files'] as $filename => $file) {
1299
            foreach ($file['messages'] as $line => $lineErrors) {
1300
                foreach ($lineErrors as $column => $colErrors) {
1301
                    foreach ($colErrors as $error) {
1302
                        $errorsShown++;
1303
 
1304
                        $source = $error['source'];
1305
                        if (isset($sources[$source]) === false) {
1306
                            $sources[$source] = 1;
1307
                        } else {
1308
                            $sources[$source]++;
1309
                        }
1310
                    }
1311
                }
1312
            }//end foreach
1313
        }//end foreach
1314
 
1315
        if ($errorsShown === 0) {
1316
            // Nothing to show.
1317
            return 0;
1318
        }
1319
 
1320
        asort($sources);
1321
        $sources = array_reverse($sources);
1322
 
1323
        echo PHP_EOL.'PHP CODE SNIFFER VIOLATION SOURCE SUMMARY'.PHP_EOL;
1324
        echo str_repeat('-', $width).PHP_EOL;
1325
        if ($showSources === true) {
1326
            echo 'SOURCE'.str_repeat(' ', ($width - 11)).'COUNT'.PHP_EOL;
1327
            echo str_repeat('-', $width).PHP_EOL;
1328
        } else {
1329
            echo 'STANDARD    CATEGORY            SNIFF'.str_repeat(' ', ($width - 42)).'COUNT'.PHP_EOL;
1330
            echo str_repeat('-', $width).PHP_EOL;
1331
        }
1332
 
1333
        foreach ($sources as $source => $count) {
1334
            if ($showSources === true) {
1335
                $source = substr($source, 0, -5);
1336
                echo $source.str_repeat(' ', ($width - 5 - strlen($source)));
1337
            } else {
1338
                $parts = explode('.', $source);
1339
 
1340
                if (strlen($parts[0]) > 10) {
1341
                    $parts[0] = substr($parts[0], 0, ((strlen($parts[0]) -10) * -1));
1342
                }
1343
                echo $parts[0].str_repeat(' ', (12 - strlen($parts[0])));
1344
 
1345
                $category = $this->makeFriendlyName($parts[1]);
1346
                if (strlen($category) > 18) {
1347
                    $category = substr($category, 0, ((strlen($category) -18) * -1));
1348
                }
1349
                echo $category.str_repeat(' ', (20 - strlen($category)));
1350
 
1351
                $sniff = substr($parts[2], 0, -5);
1352
                $sniff = $this->makeFriendlyName($sniff);
1353
                if (strlen($sniff) > ($width - 39)) {
1354
                    $sniff = substr($sniff, 0, ((strlen($sniff) - $width - 39) * -1));
1355
                }
1356
                echo $sniff.str_repeat(' ', ($width - 37 - strlen($sniff)));
1357
            }
1358
 
1359
            echo $count.PHP_EOL;
1360
        }//end foreach
1361
 
1362
        echo str_repeat('-', $width).PHP_EOL;
1363
        echo "A TOTAL OF $errorsShown SNIFF VIOLATION(S) ";
1364
        echo 'WERE FOUND IN '.count($sources).' SOURCE(S)'.PHP_EOL;
1365
        echo str_repeat('-', $width).PHP_EOL.PHP_EOL;
1366
 
1367
        return $errorsShown;
1368
 
1369
    }//end printSourceReport()
1370
 
1371
 
1372
    /**
1373
     * Prints the author of all errors and warnings, as given by "svn blame".
1374
     *
1375
     * Requires you to have the svn command in your path.
1376
     *
1377
     * @param boolean $showWarnings Show warnings as well as errors.
1378
     * @param boolean $showSources  Show error sources in report.
1379
     * @param int     $width        How wide the report should be.
1380
     *
1381
     * @return int The number of error and warning messages shown.
1382
     */
1383
    public function printSvnBlameReport($showWarnings=true, $showSources=false, $width=80)
1384
    {
1385
        $authors = array();
1386
        $praise  = array();
1387
        $sources = array();
1388
        $width   = max($width, 70);
1389
 
1390
        $errorsShown = 0;
1391
 
1392
        $report = $this->prepareErrorReport($showWarnings);
1393
        foreach ($report['files'] as $filename => $file) {
1394
            if (PHP_CODESNIFFER_VERBOSITY > 0) {
1395
                echo 'Getting SVN blame info for '.basename($filename).'... ';
1396
            }
1397
 
1398
            $command = 'svn blame '.$filename;
1399
            $handle  = popen($command, 'r');
1400
            if ($handle === false) {
1401
                echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
1402
                exit(2);
1403
            }
1404
 
1405
            $rawContent = stream_get_contents($handle);
1406
            fclose($handle);
1407
 
1408
            if (PHP_CODESNIFFER_VERBOSITY > 0) {
1409
                echo 'DONE'.PHP_EOL;
1410
            }
1411
 
1412
            $blames = explode("\n", $rawContent);
1413
 
1414
            foreach ($file['messages'] as $line => $lineErrors) {
1415
                $blameParts = array();
1416
                preg_match('|\s+([^\s]+)\s+([^\s]+)|', $blames[$line], $blameParts);
1417
 
1418
                if (isset($blameParts[2]) === false) {
1419
                    continue;
1420
                }
1421
 
1422
                $author = $blameParts[2];
1423
                if (isset($authors[$author]) === false) {
1424
                    $authors[$author] = 0;
1425
                    $praise[$author]  = array(
1426
                                         'good' => 0,
1427
                                         'bad'  => 0,
1428
                                        );
1429
                }
1430
 
1431
                $praise[$author]['bad']++;
1432
 
1433
                foreach ($lineErrors as $column => $colErrors) {
1434
                    foreach ($colErrors as $error) {
1435
                        $errorsShown++;
1436
                        $authors[$author]++;
1437
 
1438
                        if ($showSources === true) {
1439
                            $source = $error['source'];
1440
                            if (isset($sources[$author][$source]) === false) {
1441
                                $sources[$author][$source] = 1;
1442
                            } else {
1443
                                $sources[$author][$source]++;
1444
                            }
1445
                        }
1446
                    }
1447
                }
1448
 
1449
                unset($blames[$line]);
1450
            }//end foreach
1451
 
1452
            // No go through and give the authors some credit for
1453
            // all the lines that do not have errors.
1454
            foreach ($blames as $line) {
1455
                $blameParts = array();
1456
                preg_match('|\s+([^\s]+)\s+([^\s]+)|', $line, $blameParts);
1457
 
1458
                if (isset($blameParts[2]) === false) {
1459
                    continue;
1460
                }
1461
 
1462
                $author = $blameParts[2];
1463
                if (isset($authors[$author]) === false) {
1464
                    // This author doesn't have any errors.
1465
                    if (PHP_CODESNIFFER_VERBOSITY === 0) {
1466
                        continue;
1467
                    }
1468
 
1469
                    $authors[$author] = 0;
1470
                    $praise[$author]  = array(
1471
                                         'good' => 0,
1472
                                         'bad'  => 0,
1473
                                        );
1474
                }
1475
 
1476
                $praise[$author]['good']++;
1477
            }//end foreach
1478
        }//end foreach
1479
 
1480
        if ($errorsShown === 0) {
1481
            // Nothing to show.
1482
            return 0;
1483
        }
1484
 
1485
        asort($authors);
1486
        $authors = array_reverse($authors);
1487
 
1488
        echo PHP_EOL.'PHP CODE SNIFFER SVN BLAME SUMMARY'.PHP_EOL;
1489
        echo str_repeat('-', $width).PHP_EOL;
1490
        if ($showSources === true) {
1491
            echo 'AUTHOR   SOURCE'.str_repeat(' ', ($width - 27)).'COUNT (%)'.PHP_EOL;
1492
            echo str_repeat('-', $width).PHP_EOL;
1493
        } else {
1494
            echo 'AUTHOR'.str_repeat(' ', ($width - 18)).'COUNT (%)'.PHP_EOL;
1495
            echo str_repeat('-', $width).PHP_EOL;
1496
        }
1497
 
1498
        foreach ($authors as $author => $count) {
1499
            if ($praise[$author]['good'] === 0) {
1500
                $percent = 0;
1501
            } else {
1502
                $percent = round(($praise[$author]['bad'] / $praise[$author]['good'] * 100), 2);
1503
            }
1504
 
1505
            echo $author.str_repeat(' ', ($width - 12 - strlen($author)))."$count ($percent)".PHP_EOL;
1506
 
1507
            if ($showSources === true && isset($sources[$author]) === true) {
1508
                $errors = $sources[$author];
1509
                asort($errors);
1510
                $errors = array_reverse($errors);
1511
 
1512
                foreach ($errors as $source => $count) {
1513
                    if ($source === 'count') {
1514
                        continue;
1515
                    }
1516
 
1517
                    $source = substr($source, 0, -5);
1518
                    echo '         '.$source.str_repeat(' ', ($width - 21 - strlen($source))).$count.PHP_EOL;
1519
                }
1520
            }
1521
        }
1522
 
1523
        echo str_repeat('-', $width).PHP_EOL;
1524
        echo "A TOTAL OF $errorsShown SNIFF VIOLATION(S) ";
1525
        echo 'WERE COMMITTED BY '.count($authors).' AUTHOR(S)'.PHP_EOL;
1526
        echo str_repeat('-', $width).PHP_EOL.PHP_EOL;
1527
 
1528
        return $errorsShown;
1529
 
1530
    }//end printSvnBlameReport()
1531
 
1532
 
1533
    /**
1534
     * Converts a camel caps name into a readable string.
1535
     *
1536
     * @param string $name The camel caps name to convert.
1537
     *
1538
     * @return string
1539
     */
1540
    public function makeFriendlyName($name)
1541
    {
1542
        $friendlyName = '';
1543
        $length = strlen($name);
1544
 
1545
        $lastWasUpper   = false;
1546
        $lastWasNumeric = false;
1547
        for ($i = 0; $i < $length; $i++) {
1548
            if (is_numeric($name[$i]) === true) {
1549
                if ($lastWasNumeric === false) {
1550
                    $friendlyName .= ' ';
1551
                }
1552
 
1553
                $lastWasUpper   = false;
1554
                $lastWasNumeric = true;
1555
            } else {
1556
                $lastWasNumeric = false;
1557
 
1558
                $char = strtolower($name[$i]);
1559
                if ($char === $name[$i]) {
1560
                    // Lowercase.
1561
                    $lastWasUpper = false;
1562
                } else {
1563
                    // Uppercase.
1564
                    if ($lastWasUpper === false) {
1565
                        $friendlyName .= ' ';
1566
                        $next = $name[($i + 1)];
1567
                        if (strtolower($next) === $next) {
1568
                            // Next char is lowercase so it is a word boundary.
1569
                            $name[$i] = strtolower($name[$i]);
1570
                        }
1571
                    }
1572
 
1573
                    $lastWasUpper = true;
1574
                }
1575
            }//end if
1576
 
1577
            $friendlyName .= $name[$i];
1578
        }//end for
1579
 
1580
        $friendlyName    = trim($friendlyName);
1581
        $friendlyName[0] = strtoupper($friendlyName[0]);
1582
 
1583
        return $friendlyName;
1584
 
1585
    }//end makeFriendlyName()
1586
 
1587
 
1588
    /**
1589
     * Generates documentation for a coding standard.
1590
     *
1591
     * @param string $standard  The standard to generate docs for
1592
     * @param array  $sniffs    A list of sniffs to limit the docs to.
1593
     * @param string $generator The name of the generator class to use.
1594
     *
1595
     * @return void
1596
     */
1597
    public function generateDocs($standard, array $sniffs=array(), $generator='Text')
1598
    {
1599
        include_once 'PHP/CodeSniffer/DocGenerators/'.$generator.'.php';
1600
 
1601
        $class     = "PHP_CodeSniffer_DocGenerators_$generator";
1602
        $generator = new $class($standard, $sniffs);
1603
 
1604
        $generator->generate();
1605
 
1606
    }//end generateDocs()
1607
 
1608
 
1609
    /**
1610
     * Returns the PHP_CodeSniffer file objects.
1611
     *
1612
     * @return array(PHP_CodeSniffer_File)
1613
     */
1614
    public function getFiles()
1615
    {
1616
        return $this->files;
1617
 
1618
    }//end getFiles()
1619
 
1620
 
1621
    /**
1622
     * Gets the array of PHP_CodeSniffer_Sniff's.
1623
     *
1624
     * @return array(PHP_CodeSniffer_Sniff)
1625
     */
1626
    public function getSniffs()
1627
    {
1628
        return $this->listeners;
1629
 
1630
    }//end getSniffs()
1631
 
1632
 
1633
    /**
1634
     * Gets the array of PHP_CodeSniffer_Sniff's indexed by token type.
1635
     *
1636
     * @return array()
1637
     */
1638
    public function getTokenSniffs()
1639
    {
1640
        return $this->_tokenListeners;
1641
 
1642
    }//end getTokenSniffs()
1643
 
1644
 
1645
    /**
1646
     * Takes a token produced from <code>token_get_all()</code> and produces a
1647
     * more uniform token.
1648
     *
1649
     * Note that this method also resolves T_STRING tokens into more descrete
1650
     * types, therefore there is no need to call resolveTstringToken()
1651
     *
1652
     * @param string|array $token The token to convert.
1653
     *
1654
     * @return array The new token.
1655
     */
1656
    public static function standardiseToken($token)
1657
    {
1658
        if (is_array($token) === false) {
1659
            $newToken = self::resolveSimpleToken($token);
1660
        } else {
1661
            switch ($token[0]) {
1662
            case T_STRING:
1663
                // Some T_STRING tokens can be more specific.
1664
                $newToken = self::resolveTstringToken($token);
1665
                break;
1666
            case T_CURLY_OPEN:
1667
                $newToken            = array();
1668
                $newToken['code']    = T_OPEN_CURLY_BRACKET;
1669
                $newToken['content'] = $token[1];
1670
                $newToken['type']    = 'T_OPEN_CURLY_BRACKET';
1671
                break;
1672
            default:
1673
                $newToken            = array();
1674
                $newToken['code']    = $token[0];
1675
                $newToken['content'] = $token[1];
1676
                $newToken['type']    = token_name($token[0]);
1677
                break;
1678
            }//end switch
1679
        }//end if
1680
 
1681
        return $newToken;
1682
 
1683
    }//end standardiseToken()
1684
 
1685
 
1686
    /**
1687
     * Converts T_STRING tokens into more usable token names.
1688
     *
1689
     * The token should be produced using the token_get_all() function.
1690
     * Currently, not all T_STRING tokens are converted.
1691
     *
1692
     * @param string|array $token The T_STRING token to convert as constructed
1693
     *                            by token_get_all().
1694
     *
1695
     * @return array The new token.
1696
     */
1697
    public static function resolveTstringToken(array $token)
1698
    {
1699
        $newToken = array();
1700
        switch (strtolower($token[1])) {
1701
        case 'false':
1702
            $newToken['type'] = 'T_FALSE';
1703
            break;
1704
        case 'true':
1705
            $newToken['type'] = 'T_TRUE';
1706
            break;
1707
        case 'null':
1708
            $newToken['type'] = 'T_NULL';
1709
            break;
1710
        case 'self':
1711
            $newToken['type'] = 'T_SELF';
1712
            break;
1713
        case 'parent':
1714
            $newToken['type'] = 'T_PARENT';
1715
            break;
1716
        default:
1717
            $newToken['type'] = 'T_STRING';
1718
            break;
1719
        }
1720
 
1721
        $newToken['code']    = constant($newToken['type']);
1722
        $newToken['content'] = $token[1];
1723
 
1724
        return $newToken;
1725
 
1726
    }//end resolveTstringToken()
1727
 
1728
 
1729
    /**
1730
     * Converts simple tokens into a format that conforms to complex tokens
1731
     * produced by token_get_all().
1732
     *
1733
     * Simple tokens are tokens that are not in array form when produced from
1734
     * token_get_all().
1735
     *
1736
     * @param string $token The simple token to convert.
1737
     *
1738
     * @return array The new token in array format.
1739
     */
1740
    public static function resolveSimpleToken($token)
1741
    {
1742
        $newToken = array();
1743
 
1744
        switch ($token) {
1745
        case '{':
1746
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1747
            break;
1748
        case '}':
1749
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1750
            break;
1751
        case '[':
1752
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1753
            break;
1754
        case ']':
1755
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1756
            break;
1757
        case '(':
1758
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1759
            break;
1760
        case ')':
1761
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1762
            break;
1763
        case ':':
1764
            $newToken['type'] = 'T_COLON';
1765
            break;
1766
        case '.':
1767
            $newToken['type'] = 'T_STRING_CONCAT';
1768
            break;
1769
        case '?':
1770
            $newToken['type'] = 'T_INLINE_THEN';
1771
            break;
1772
        case ';':
1773
            $newToken['type'] = 'T_SEMICOLON';
1774
            break;
1775
        case '=':
1776
            $newToken['type'] = 'T_EQUAL';
1777
            break;
1778
        case '*':
1779
            $newToken['type'] = 'T_MULTIPLY';
1780
            break;
1781
        case '/':
1782
            $newToken['type'] = 'T_DIVIDE';
1783
            break;
1784
        case '+':
1785
            $newToken['type'] = 'T_PLUS';
1786
            break;
1787
        case '-':
1788
            $newToken['type'] = 'T_MINUS';
1789
            break;
1790
        case '%':
1791
            $newToken['type'] = 'T_MODULUS';
1792
            break;
1793
        case '^':
1794
            $newToken['type'] = 'T_POWER';
1795
            break;
1796
        case '&':
1797
            $newToken['type'] = 'T_BITWISE_AND';
1798
            break;
1799
        case '|':
1800
            $newToken['type'] = 'T_BITWISE_OR';
1801
            break;
1802
        case '<':
1803
            $newToken['type'] = 'T_LESS_THAN';
1804
            break;
1805
        case '>':
1806
            $newToken['type'] = 'T_GREATER_THAN';
1807
            break;
1808
        case '!':
1809
            $newToken['type'] = 'T_BOOLEAN_NOT';
1810
            break;
1811
        case ',':
1812
            $newToken['type'] = 'T_COMMA';
1813
            break;
1814
        case '@':
1815
            $newToken['type'] = 'T_ASPERAND';
1816
            break;
1817
        default:
1818
            $newToken['type'] = 'T_NONE';
1819
            break;
1820
        }//end switch
1821
 
1822
        $newToken['code']    = constant($newToken['type']);
1823
        $newToken['content'] = $token;
1824
 
1825
        return $newToken;
1826
 
1827
    }//end resolveSimpleToken()
1828
 
1829
 
1830
    /**
1831
     * Returns true if the specified string is in the camel caps format.
1832
     *
1833
     * @param string  $string      The string the verify.
1834
     * @param boolean $classFormat If true, check to see if the string is in the
1835
     *                             class format. Class format strings must start
1836
     *                             with a capital letter and contain no
1837
     *                             underscores.
1838
     * @param boolean $public      If true, the first character in the string
1839
     *                             must be an a-z character. If false, the
1840
     *                             character must be an underscore. This
1841
     *                             argument is only applicable if $classFormat
1842
     *                             is false.
1843
     * @param boolean $strict      If true, the string must not have two captial
1844
     *                             letters next to each other. If false, a
1845
     *                             relaxed camel caps policy is used to allow
1846
     *                             for acronyms.
1847
     *
1848
     * @return boolean
1849
     */
1850
    public static function isCamelCaps(
1851
        $string,
1852
        $classFormat=false,
1853
        $public=true,
1854
        $strict=true
1855
    ) {
1856
        // Check the first character first.
1857
        if ($classFormat === false) {
1858
            if ($public === false) {
1859
                $legalFirstChar = '[_][a-z]';
1860
            } else {
1861
                $legalFirstChar = '[a-z]';
1862
            }
1863
        } else {
1864
            $legalFirstChar = '[A-Z]';
1865
        }
1866
 
1867
        if (preg_match("|^$legalFirstChar|", $string) === 0) {
1868
            return false;
1869
        }
1870
 
1871
        // Check that the name only contains legal characters.
1872
        if ($classFormat === false) {
1873
            $legalChars = 'a-zA-Z0-9';
1874
        } else {
1875
            $legalChars = 'a-zA-Z';
1876
        }
1877
 
1878
        if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
1879
            return false;
1880
        }
1881
 
1882
        if ($strict === true) {
1883
            // Check that there are not two captial letters next to each other.
1884
            $length          = strlen($string);
1885
            $lastCharWasCaps = $classFormat;
1886
 
1887
            for ($i = 1; $i < $length; $i++) {
1888
                $ascii = ord($string{$i});
1889
                if ($ascii >= 48 && $ascii <= 57) {
1890
                    // The character is a number, so it cant be a captial.
1891
                    $isCaps = false;
1892
                } else {
1893
                    if (strtoupper($string{$i}) === $string{$i}) {
1894
                        $isCaps = true;
1895
                    } else {
1896
                        $isCaps = false;
1897
                    }
1898
                }
1899
 
1900
                if ($isCaps === true && $lastCharWasCaps === true) {
1901
                    return false;
1902
                }
1903
 
1904
                $lastCharWasCaps = $isCaps;
1905
            }
1906
        }//end if
1907
 
1908
        return true;
1909
 
1910
    }//end isCamelCaps()
1911
 
1912
 
1913
    /**
1914
     * Returns true if the specified string is in the underscore caps format.
1915
     *
1916
     * @param string $string The string to verify.
1917
     *
1918
     * @return boolean
1919
     */
1920
    public static function isUnderscoreName($string)
1921
    {
1922
        // If there are space in the name, it can't be valid.
1923
        if (strpos($string, ' ') !== false) {
1924
            return false;
1925
        }
1926
 
1927
        $validName = true;
1928
        $nameBits  = explode('_', $string);
1929
 
1930
        if (preg_match('|^[A-Z]|', $string) === 0) {
1931
            // Name does not begin with a capital letter.
1932
            $validName = false;
1933
        } else {
1934
            foreach ($nameBits as $bit) {
1935
                if ($bit{0} !== strtoupper($bit{0})) {
1936
                    $validName = false;
1937
                    break;
1938
                }
1939
            }
1940
        }
1941
 
1942
        return $validName;
1943
 
1944
    }//end isUnderscoreName()
1945
 
1946
 
1947
    /**
1948
     * Returns a valid variable type for param/var tag.
1949
     *
1950
     * If type is not one of the standard type, it must be a custom type.
1951
     * Returns the correct type name suggestion if type name is invalid.
1952
     *
1953
     * @param string $varType The variable type to process.
1954
     *
1955
     * @return string
1956
     */
1957
    public static function suggestType($varType)
1958
    {
1959
        if ($varType === '') {
1960
            return '';
1961
        }
1962
 
1963
        if (in_array($varType, self::$allowedTypes) === true) {
1964
            return $varType;
1965
        } else {
1966
            $lowerVarType = strtolower($varType);
1967
            switch ($lowerVarType) {
1968
            case 'bool':
1969
                return 'boolean';
1970
            case 'double':
1971
            case 'real':
1972
                return 'float';
1973
            case 'int':
1974
                return 'integer';
1975
            case 'array()':
1976
                return 'array';
1977
            }//end switch
1978
 
1979
            if (strpos($lowerVarType, 'array(') !== false) {
1980
                // Valid array declaration:
1981
                // array, array(type), array(type1 => type2).
1982
                $matches = array();
1983
                $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
1984
                if (preg_match($pattern, $varType, $matches) !== 0) {
1985
                    $type1 = '';
1986
                    if (isset($matches[1]) === true) {
1987
                        $type1 = $matches[1];
1988
                    }
1989
 
1990
                    $type2 = '';
1991
                    if (isset($matches[3]) === true) {
1992
                        $type2 = $matches[3];
1993
                    }
1994
 
1995
                    $type1 = self::suggestType($type1);
1996
                    $type2 = self::suggestType($type2);
1997
                    if ($type2 !== '') {
1998
                        $type2 = ' => '.$type2;
1999
                    }
2000
 
2001
                    return "array($type1$type2)";
2002
                } else {
2003
                    return 'array';
2004
                }//end if
2005
            } else if (in_array($lowerVarType, self::$allowedTypes) === true) {
2006
                // A valid type, but not lower cased.
2007
                return $lowerVarType;
2008
            } else {
2009
                // Must be a custom type name.
2010
                return $varType;
2011
            }//end if
2012
        }//end if
2013
 
2014
    }//end suggestType()
2015
 
2016
 
2017
    /**
2018
     * Get a list of all coding standards installed.
2019
     *
2020
     * Coding standards are directories located in the
2021
     * CodeSniffer/Standards directory. Valid coding standards
2022
     * include a Sniffs subdirectory.
2023
     *
2024
     * @param boolean $includeGeneric If true, the special "Generic"
2025
     *                                coding standard will be included
2026
     *                                if installed.
2027
     * @param string  $standardsDir   A specific directory to look for standards
2028
     *                                in. If not specified, PHP_CodeSniffer will
2029
     *                                look in its default location.
2030
     *
2031
     * @return array
2032
     * @see isInstalledStandard()
2033
     */
2034
    public static function getInstalledStandards(
2035
        $includeGeneric=false,
2036
        $standardsDir=''
2037
    ) {
2038
        $installedStandards = array();
2039
 
2040
        if ($standardsDir === '') {
2041
            $standardsDir = dirname(__FILE__).'/CodeSniffer/Standards';
2042
        }
2043
 
2044
        $di = new DirectoryIterator($standardsDir);
2045
        foreach ($di as $file) {
2046
            if ($file->isDir() === true && $file->isDot() === false) {
2047
                $filename = $file->getFilename();
2048
 
2049
                // Ignore the special "Generic" standard.
2050
                if ($includeGeneric === false && $filename === 'Generic') {
2051
                    continue;
2052
                }
2053
 
2054
                // Valid coding standard dirs include a standard class.
2055
                $csFile = $file->getPathname()."/{$filename}CodingStandard.php";
2056
                if (is_file($csFile) === true) {
2057
                    // We found a coding standard directory.
2058
                    $installedStandards[] = $filename;
2059
                }
2060
            }
2061
        }
2062
 
2063
        return $installedStandards;
2064
 
2065
    }//end getInstalledStandards()
2066
 
2067
 
2068
    /**
2069
     * Determine if a standard is installed.
2070
     *
2071
     * Coding standards are directories located in the
2072
     * CodeSniffer/Standards directory. Valid coding standards
2073
     * include a Sniffs subdirectory.
2074
     *
2075
     * @param string $standard The name of the coding standard.
2076
     *
2077
     * @return boolean
2078
     * @see getInstalledStandards()
2079
     */
2080
    public static function isInstalledStandard($standard)
2081
    {
2082
        $standardDir  = dirname(__FILE__);
2083
        $standardDir .= '/CodeSniffer/Standards/'.$standard;
2084
        if (is_file("$standardDir/{$standard}CodingStandard.php") === true) {
2085
            return true;
2086
        } else {
2087
            // This could be a custom standard, installed outside our
2088
            // standards directory.
2089
            $standardFile = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.basename($standard).'CodingStandard.php';
2090
            return (is_file($standardFile) === true);
2091
        }
2092
 
2093
    }//end isInstalledStandard()
2094
 
2095
 
2096
    /**
2097
     * Get a single config value.
2098
     *
2099
     * Config data is stored in the data dir, in a file called
2100
     * CodeSniffer.conf. It is a simple PHP array.
2101
     *
2102
     * @param string $key The name of the config value.
2103
     *
2104
     * @return string
2105
     * @see setConfigData()
2106
     * @see getAllConfigData()
2107
     */
2108
    public static function getConfigData($key)
2109
    {
2110
        $phpCodeSnifferConfig = self::getAllConfigData();
2111
 
2112
        if ($phpCodeSnifferConfig === null) {
2113
            return null;
2114
        }
2115
 
2116
        if (isset($phpCodeSnifferConfig[$key]) === false) {
2117
            return null;
2118
        }
2119
 
2120
        return $phpCodeSnifferConfig[$key];
2121
 
2122
    }//end getConfigData()
2123
 
2124
 
2125
    /**
2126
     * Set a single config value.
2127
     *
2128
     * Config data is stored in the data dir, in a file called
2129
     * CodeSniffer.conf. It is a simple PHP array.
2130
     *
2131
     * @param string      $key   The name of the config value.
2132
     * @param string|null $value The value to set. If null, the config
2133
     *                           entry is deleted, reverting it to the
2134
     *                           default value.
2135
     * @param boolean     $temp  Set this config data temporarily for this
2136
     *                           script run. This will not write the config
2137
     *                           data to the config file.
2138
     *
2139
     * @return boolean
2140
     * @see getConfigData()
2141
     * @throws PHP_CodeSniffer_Exception If the config file can not be written.
2142
     */
2143
    public static function setConfigData($key, $value, $temp=false)
2144
    {
2145
        if ($temp === false) {
2146
            $configFile = dirname(__FILE__).'/CodeSniffer.conf';
2147
            if (is_file($configFile) === false) {
2148
                $configFile = '/usr/share/php/data/PHP_CodeSniffer/CodeSniffer.conf';
2149
            }
2150
 
2151
            if (is_file($configFile) === true
2152
                && is_writable($configFile) === false
2153
            ) {
2154
                $error = "Config file $configFile is not writable";
2155
                throw new PHP_CodeSniffer_Exception($error);
2156
            }
2157
        }
2158
 
2159
        $phpCodeSnifferConfig = self::getAllConfigData();
2160
 
2161
        if ($value === null) {
2162
            if (isset($phpCodeSnifferConfig[$key]) === true) {
2163
                unset($phpCodeSnifferConfig[$key]);
2164
            }
2165
        } else {
2166
            $phpCodeSnifferConfig[$key] = $value;
2167
        }
2168
 
2169
        if ($temp === false) {
2170
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
2171
            $output .= var_export($phpCodeSnifferConfig, true);
2172
            $output .= "\n?".'>';
2173
 
2174
            if (file_put_contents($configFile, $output) === false) {
2175
                return false;
2176
            }
2177
        }
2178
 
2179
        $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
2180
 
2181
        return true;
2182
 
2183
    }//end setConfigData()
2184
 
2185
 
2186
    /**
2187
     * Get all config data in an array.
2188
     *
2189
     * @return string
2190
     * @see getConfigData()
2191
     */
2192
    public static function getAllConfigData()
2193
    {
2194
        if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) {
2195
            return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
2196
        }
2197
 
2198
        $configFile = dirname(__FILE__).'/CodeSniffer.conf';
2199
        if (is_file($configFile) === false) {
2200
            $configFile = '/usr/share/php/data/PHP_CodeSniffer/CodeSniffer.conf';
2201
        }
2202
 
2203
        if (is_file($configFile) === false) {
2204
            return null;
2205
        }
2206
 
2207
        include $configFile;
2208
        $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
2209
        return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
2210
 
2211
    }//end getAllConfigData()
2212
 
2213
 
2214
}//end class
2215
 
2216
?>