Completed
Pull Request — master (#291)
by Juliette
02:26
created
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * PHPCompatibility_Sniff.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Wim Godden <[email protected]>
10
 * @copyright 2014 Cu.be Solutions bvba
11
 */
12
13
/**
14
 * PHPCompatibility_Sniff.
15
 *
16
 * @category  PHP
17
 * @package   PHPCompatibility
18
 * @author    Wim Godden <[email protected]>
19
 * @version   1.1.0
20
 * @copyright 2014 Cu.be Solutions bvba
21
 */
22
abstract class PHPCompatibility_Sniff implements PHP_CodeSniffer_Sniff
23
{
24
25
    /**
26
     * List of functions using hash algorithm as parameter (always the first parameter).
27
     *
28
     * Used by the new/removed hash algorithm sniffs.
29
     * Key is the function name, value is the 1-based parameter position in the function call.
30
     *
31
     * @var array
32
     */
33
    protected $hashAlgoFunctions = array(
34
        'hash_file'      => 1,
35
        'hash_hmac_file' => 1,
36
        'hash_hmac'      => 1,
37
        'hash_init'      => 1,
38
        'hash_pbkdf2'    => 1,
39
        'hash'           => 1,
40
    );
41
42
43
    /**
44
     * List of functions which take an ini directive as parameter (always the first parameter).
45
     *
46
     * Used by the new/removed ini directives sniffs.
47
     * Key is the function name, value is the 1-based parameter position in the function call.
48
     *
49
     * @var array
50
     */
51
    protected $iniFunctions = array(
52
        'ini_get' => 1,
53
        'ini_set' => 1,
54
    );
55
56
57
/* The testVersion configuration variable may be in any of the following formats:
58
 * 1) Omitted/empty, in which case no version is specified.  This effectively
59
 *    disables all the checks provided by this standard.
60
 * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
61
 *    the code will run on that version of PHP (no deprecated features or newer
62
 *    features being used).
63
 * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
64
 *    on all PHP versions in that range, and that it doesn't use any features that
65
 *    were deprecated by the final version in the list, or which were not available
66
 *    for the first version in the list.
67
 * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
68
 * would be treated as invalid, and ignored.
69
 * This standard doesn't support checking against PHP4, so the minimum version that
70
 * is recognised is "5.0".
71
 */
72
73
    private function getTestVersion()
74
    {
75
        /**
76
         * var $arrTestVersions will hold an array containing min/max version of PHP
77
         *   that we are checking against (see above).  If only a single version
78
         *   number is specified, then this is used as both the min and max.
79
         */
80
        static $arrTestVersions = array();
81
82
        $testVersion = trim(PHP_CodeSniffer::getConfigData('testVersion'));
83
84
        if (!isset($arrTestVersions[$testVersion]) && !empty($testVersion)) {
85
86
            $arrTestVersions[$testVersion] = array(null, null);
87
            if (preg_match('/^\d+\.\d+$/', $testVersion)) {
88
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
89
            }
90
            elseif (preg_match('/^(\d+\.\d+)\s*-\s*(\d+\.\d+)$/', $testVersion,
91
                               $matches))
92
            {
93
                if (version_compare($matches[1], $matches[2], '>')) {
94
                    trigger_error("Invalid range in testVersion setting: '"
95
                                  . $testVersion . "'", E_USER_WARNING);
96
                }
97
                else {
98
                    $arrTestVersions[$testVersion] = array($matches[1], $matches[2]);
99
                }
100
            }
101
            elseif (!$testVersion == '') {
102
                trigger_error("Invalid testVersion setting: '" . $testVersion
103
                              . "'", E_USER_WARNING);
104
            }
105
        }
106
107
        if (isset($arrTestVersions[$testVersion])) {
108
            return $arrTestVersions[$testVersion];
109
        }
110
        else {
111
            return array(null, null);
112
        }
113
    }
114
115 View Code Duplication
    public function supportsAbove($phpVersion)
116
    {
117
        $testVersion = $this->getTestVersion();
118
        $testVersion = $testVersion[1];
119
120
        if (is_null($testVersion)
121
            || version_compare($testVersion, $phpVersion) >= 0
122
        ) {
123
            return true;
124
        } else {
125
            return false;
126
        }
127
    }//end supportsAbove()
128
129 View Code Duplication
    public function supportsBelow($phpVersion)
130
    {
131
        $testVersion = $this->getTestVersion();
132
        $testVersion = $testVersion[0];
133
134
        if (!is_null($testVersion)
135
            && version_compare($testVersion, $phpVersion) <= 0
136
        ) {
137
            return true;
138
        } else {
139
            return false;
140
        }
141
    }//end supportsBelow()
142
143
144
    /**
145
     * Add a PHPCS message to the output stack as either a warning or an error.
146
     *
147
     * @param PHP_CodeSniffer_File $phpcsFile The file the message applies to.
148
     * @param string               $message   The message.
149
     * @param int                  $stackPtr  The position of the token
150
     *                                        the message relates to.
151
     * @param bool                 $isError   Whether to report the message as an
152
     *                                        'error' or 'warning'.
153
     *                                        Defaults to true (error).
154
     * @param string               $code      The error code for the message.
155
     *                                        Defaults to 'Found'.
156
     * @param array                $data      Optional input for the data replacements.
157
     *
158
     * @return void.
0 ignored issues
show
The doc-type void. could not be parsed: Unknown type name "void." 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...
159
     */
160
    public function addMessage($phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
161
    {
162
        if ($isError === true) {
163
            $phpcsFile->addError($message, $stackPtr, $code, $data);
164
        } else {
165
            $phpcsFile->addWarning($message, $stackPtr, $code, $data);
166
        }
167
    }
168
169
170
    /**
171
     * Convert an arbitrary string to an alphanumeric string with underscores.
172
     *
173
     * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
174
     *
175
     * @param string $baseString Arbitrary string.
176
     *
177
     * @return string
178
     */
179
    public function stringToErrorCode($baseString)
180
    {
181
        return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
182
    }
183
184
185
    /**
186
     * Strip quotes surrounding an arbitrary string.
187
     *
188
     * Intended for use with the content of a T_CONSTANT_ENCAPSED_STRING.
189
     *
190
     * @param string $string The raw string.
191
     *
192
     * @return string String without quotes around it.
193
     */
194
    public function stripQuotes($string) {
195
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
196
    }
197
198
199
    /**
200
     * Make all top level array keys in an array lowercase.
201
     *
202
     * @param array $array Initial array.
203
     *
204
     * @return array Same array, but with all lowercase top level keys.
205
     */
206
    public function arrayKeysToLowercase($array)
207
    {
208
        $keys = array_keys($array);
209
        $keys = array_map('strtolower', $keys);
210
        return array_combine($keys, $array);
211
    }
212
213
214
    /**
215
     * Returns the name(s) of the interface(s) that the specified class implements.
216
     *
217
     * Returns FALSE on error or if there are no implemented interface names.
218
     *
219
     * {@internal Duplicate of same method as introduced in PHPCS 2.7.
220
     * Once the minimum supported PHPCS version for this sniff library goes beyond
221
     * that, this method can be removed and call to it replaced with
222
     * `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}}
223
     *
224
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
225
     * @param int                  $stackPtr  The position of the class token.
226
     *
227
     * @return array|false
228
     */
229
    public function findImplementedInterfaceNames(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
230
    {
231
        if (method_exists($phpcsFile, 'findImplementedInterfaceNames')) {
232
            return $phpcsFile->findImplementedInterfaceNames($stackPtr);
233
        }
234
235
        $tokens = $phpcsFile->getTokens();
236
237
        // Check for the existence of the token.
238
        if (isset($tokens[$stackPtr]) === false) {
239
            return false;
240
        }
241
242
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
243
            return false;
244
        }
245
246
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
247
            return false;
248
        }
249
250
        $classOpenerIndex = $tokens[$stackPtr]['scope_opener'];
251
        $implementsIndex  = $phpcsFile->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
252
        if ($implementsIndex === false) {
253
            return false;
254
        }
255
256
        $find = array(
257
                 T_NS_SEPARATOR,
258
                 T_STRING,
259
                 T_WHITESPACE,
260
                 T_COMMA,
261
                );
262
263
        $end  = $phpcsFile->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
264
        $name = $phpcsFile->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
265
        $name = trim($name);
266
267
        if ($name === '') {
268
            return false;
269
        } else {
270
            $names = explode(',', $name);
271
            $names = array_map('trim', $names);
272
            return $names;
273
        }
274
275
    }//end findImplementedInterfaceNames()
276
277
278
    /**
279
     * Checks if a function call has parameters.
280
     *
281
     * Expects to be passed the T_STRING stack pointer for the function call.
282
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
283
     *
284
     * @link https://github.com/wimg/PHPCompatibility/issues/120
285
     * @link https://github.com/wimg/PHPCompatibility/issues/152
286
     *
287
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
288
     * @param int                  $stackPtr  The position of the function call token.
289
     *
290
     * @return bool
291
     */
292
    public function doesFunctionCallHaveParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
293
    {
294
        $tokens = $phpcsFile->getTokens();
295
296
        // Check for the existence of the token.
297
        if (isset($tokens[$stackPtr]) === false) {
298
            return false;
299
        }
300
301
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
302
            return false;
303
        }
304
305
        // Next non-empty token should be the open parenthesis.
306
        $openParenthesis = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
307
        if ($openParenthesis === false || $tokens[$openParenthesis]['code'] !== T_OPEN_PARENTHESIS) {
308
            return false;
309
        }
310
311
        if (isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
312
            return false;
313
        }
314
315
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
316
        $nextNonEmpty     = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openParenthesis + 1, $closeParenthesis + 1, true);
317
318
        if ($nextNonEmpty === $closeParenthesis) {
319
            // No parameters.
320
            return false;
321
        }
322
323
        return true;
324
    }
325
326
327
    /**
328
     * Count the number of parameters a function call has been passed.
329
     *
330
     * Expects to be passed the T_STRING stack pointer for the function call.
331
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
332
     *
333
     * @link https://github.com/wimg/PHPCompatibility/issues/111
334
     * @link https://github.com/wimg/PHPCompatibility/issues/114
335
     * @link https://github.com/wimg/PHPCompatibility/issues/151
336
     *
337
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
338
     * @param int                  $stackPtr  The position of the function call token.
339
     *
340
     * @return int
341
     */
342
    public function getFunctionCallParameterCount(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
343
    {
344
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
345
            return 0;
346
        }
347
348
        return count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
349
    }
350
351
352
    /**
353
     * Get information on all parameters passed to a function call.
354
     *
355
     * Expects to be passed the T_STRING stack pointer for the function call.
356
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
357
     *
358
     * Will return an multi-dimentional array with the start token pointer, end token
359
     * pointer and raw parameter value for all parameters. Index will be 1-based.
360
     * If no parameters are found, will return an empty array.
361
     *
362
     * @param PHP_CodeSniffer_File $phpcsFile     The file being scanned.
363
     * @param int                  $stackPtr      The position of the function call token.
364
     *
365
     * @return array
366
     */
367
    public function getFunctionCallParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
368
    {
369
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
370
            return array();
371
        }
372
373
        // Ok, we know we have a T_STRING with parameters and valid open & close parenthesis.
374
        $tokens = $phpcsFile->getTokens();
375
376
        $openParenthesis  = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
377
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
378
379
        // Which nesting level is the one we are interested in ?
380
        $nestedParenthesisCount = 1;
381 View Code Duplication
        if (isset($tokens[$openParenthesis]['nested_parenthesis'])) {
382
            $nestedParenthesisCount = count($tokens[$openParenthesis]['nested_parenthesis']) + 1;
383
        }
384
385
        $parameters = array();
386
        $nextComma  = $openParenthesis;
387
        $paramStart = $openParenthesis + 1;
388
        $cnt        = 1;
389
        while ($nextComma = $phpcsFile->findNext(array(T_COMMA, T_CLOSE_PARENTHESIS, T_OPEN_SHORT_ARRAY), $nextComma + 1, $closeParenthesis + 1)) {
390
            // Ignore anything within short array definition brackets.
391
            if (
392
                $tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
393
                &&
394
                ( isset($tokens[$nextComma]['bracket_opener']) && $tokens[$nextComma]['bracket_opener'] === $nextComma )
395
                &&
396
                isset($tokens[$nextComma]['bracket_closer'])
397
            ) {
398
                // Skip forward to the end of the short array definition.
399
                $nextComma = $tokens[$nextComma]['bracket_closer'];
400
                continue;
401
            }
402
403
            // Ignore comma's at a lower nesting level.
404
            if (
405
                $tokens[$nextComma]['type'] === 'T_COMMA'
406
                &&
407
                isset($tokens[$nextComma]['nested_parenthesis'])
408
                &&
409
                count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
410
            ) {
411
                continue;
412
            }
413
414
            // Ignore closing parenthesis if not 'ours'.
415
            if ($tokens[$nextComma]['type'] === 'T_CLOSE_PARENTHESIS' && $nextComma !== $closeParenthesis) {
416
                continue;
417
            }
418
419
            // Ok, we've reached the end of the parameter.
420
            $parameters[$cnt]['start'] = $paramStart;
421
            $parameters[$cnt]['end']   = $nextComma - 1;
422
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
423
424
            // Check if there are more tokens before the closing parenthesis.
425
            // Prevents code like the following from setting a third parameter:
426
            // functionCall( $param1, $param2, );
427
            $hasNextParam = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $nextComma + 1, $closeParenthesis, true, null, true);
428
            if ($hasNextParam === false) {
429
                break;
430
            }
431
432
            // Prepare for the next parameter.
433
            $paramStart = $nextComma + 1;
434
            $cnt++;
435
        }
436
437
        return $parameters;
438
    }
439
440
441
    /**
442
     * Get information on a specific parameter passed to a function call.
443
     *
444
     * Expects to be passed the T_STRING stack pointer for the function call.
445
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
446
     *
447
     * Will return a array with the start token pointer, end token pointer and the raw value
448
     * of the parameter at a specific offset.
449
     * If the specified parameter is not found, will return false.
450
     *
451
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
452
     * @param int                  $stackPtr    The position of the function call token.
453
     * @param int                  $paramOffset The 1-based index position of the parameter to retrieve.
454
     *
455
     * @return array|false
456
     */
457
    public function getFunctionCallParameter(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $paramOffset)
458
    {
459
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
460
461
        if (isset($parameters[$paramOffset]) === false) {
462
            return false;
463
        }
464
        else {
465
            return $parameters[$paramOffset];
466
        }
467
    }
468
469
470
    /**
471
     * Verify whether a token is within a scoped condition.
472
     *
473
     * If the optional $validScopes parameter has been passed, the function
474
     * will check that the token has at least one condition which is of a
475
     * type defined in $validScopes.
476
     *
477
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
478
     * @param int                  $stackPtr    The position of the token.
479
     * @param array|int            $validScopes Optional. Array of valid scopes
480
     *                                          or int value of a valid scope.
481
     *                                          Pass the T_.. constant(s) for the
482
     *                                          desired scope to this parameter.
483
     *
484
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
485
     *              If the $scopeTypes are set: True if *one* of the conditions is a
486
     *              valid scope, false otherwise.
487
     */
488
    public function tokenHasScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes = null)
489
    {
490
        $tokens = $phpcsFile->getTokens();
491
492
        // Check for the existence of the token.
493
        if (isset($tokens[$stackPtr]) === false) {
494
            return false;
495
        }
496
497
        // No conditions = no scope.
498
        if (empty($tokens[$stackPtr]['conditions'])) {
499
            return false;
500
        }
501
502
        // Ok, there are conditions, do we have to check for specific ones ?
503
        if (isset($validScopes) === false) {
504
            return true;
505
        }
506
507
        if (is_int($validScopes)) {
508
            // Received an integer, so cast to array.
509
            $validScopes = (array) $validScopes;
510
        }
511
512
        if (empty($validScopes) || is_array($validScopes) === false) {
513
            // No valid scope types received, so will not comply.
514
            return false;
515
        }
516
517
        // Check for required scope types.
518
        foreach ($tokens[$stackPtr]['conditions'] as $pointer => $tokenCode) {
519
            if (in_array($tokenCode, $validScopes, true)) {
520
                return true;
521
            }
522
        }
523
524
        return false;
525
    }
526
527
528
    /**
529
     * Verify whether a token is within a class scope.
530
     *
531
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
532
     * @param int                  $stackPtr  The position of the token.
533
     * @param bool                 $strict    Whether to strictly check for the T_CLASS
534
     *                                        scope or also accept interfaces and traits
535
     *                                        as scope.
536
     *
537
     * @return bool True if within class scope, false otherwise.
538
     */
539
    public function inClassScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $strict = true)
540
    {
541
        $validScopes = array(T_CLASS);
542
        if ($strict === false) {
543
            $validScopes[] = T_INTERFACE;
544
            $validScopes[] = T_TRAIT;
545
        }
546
547
        return $this->tokenHasScope($phpcsFile, $stackPtr, $validScopes);
548
    }
549
550
551
    /**
552
     * Verify whether a token is within a scoped use statement.
553
     *
554
     * PHPCS cross-version compatibility method.
555
     *
556
     * In PHPCS 1.x no conditions are set for a scoped use statement.
557
     * This method works around that limitation.
558
     *
559
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
560
     * @param int                  $stackPtr  The position of the token.
561
     *
562
     * @return bool True if within use scope, false otherwise.
563
     */
564
    public function inUseScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
565
    {
566
        static $isLowPHPCS, $ignoreTokens;
567
568
        if (isset($isLowPHPCS) === false) {
569
            $isLowPHPCS = version_compare(PHP_CodeSniffer::VERSION, '2.0', '<');
570
        }
571
        if (isset($ignoreTokens) === false) {
572
            $ignoreTokens              = PHP_CodeSniffer_Tokens::$emptyTokens;
573
            $ignoreTokens[T_STRING]    = T_STRING;
574
            $ignoreTokens[T_AS]        = T_AS;
575
            $ignoreTokens[T_PUBLIC]    = T_PUBLIC;
576
            $ignoreTokens[T_PROTECTED] = T_PROTECTED;
577
            $ignoreTokens[T_PRIVATE]   = T_PRIVATE;
578
        }
579
580
        // PHPCS 2.0.
581
        if ($isLowPHPCS === false) {
582
            return $this->tokenHasScope($phpcsFile, $stackPtr, T_USE);
583
        } else {
584
            // PHPCS 1.x.
585
            $tokens         = $phpcsFile->getTokens();
586
            $maybeCurlyOpen = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
587
            if ($tokens[$maybeCurlyOpen]['code'] === T_OPEN_CURLY_BRACKET) {
588
                $maybeUseStatement = $phpcsFile->findPrevious($ignoreTokens, ($maybeCurlyOpen - 1), null, true);
589
                if ($tokens[$maybeUseStatement]['code'] === T_USE) {
590
                    return true;
591
                }
592
            }
593
            return false;
594
        }
595
    }
596
597
598
    /**
599
     * Returns the fully qualified class name for a new class instantiation.
600
     *
601
     * Returns an empty string if the class name could not be reliably inferred.
602
     *
603
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
604
     * @param int                  $stackPtr  The position of a T_NEW token.
605
     *
606
     * @return string
607
     */
608
    public function getFQClassNameFromNewToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
609
    {
610
        $tokens = $phpcsFile->getTokens();
611
612
        // Check for the existence of the token.
613
        if (isset($tokens[$stackPtr]) === false) {
614
            return '';
615
        }
616
617
        if ($tokens[$stackPtr]['code'] !== T_NEW) {
618
            return '';
619
        }
620
621
        $find = array(
622
                 T_NS_SEPARATOR,
623
                 T_STRING,
624
                 T_NAMESPACE,
625
                 T_WHITESPACE,
626
                );
627
628
        $start = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
629
        // Bow out if the next token is a variable as we don't know where it was defined.
630
        if ($tokens[$start]['code'] === T_VARIABLE) {
631
            return '';
632
        }
633
634
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
635
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
636
        $className = trim($className);
637
638
        return $this->getFQName($phpcsFile, $stackPtr, $className);
639
    }
640
641
642
    /**
643
     * Returns the fully qualified name of the class that the specified class extends.
644
     *
645
     * Returns an empty string if the class does not extend another class or if
646
     * the class name could not be reliably inferred.
647
     *
648
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
649
     * @param int                  $stackPtr  The position of a T_CLASS token.
650
     *
651
     * @return string
652
     */
653
    public function getFQExtendedClassName(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
654
    {
655
        $tokens = $phpcsFile->getTokens();
656
657
        // Check for the existence of the token.
658
        if (isset($tokens[$stackPtr]) === false) {
659
            return '';
660
        }
661
662
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
663
            return '';
664
        }
665
666
        $extends = $phpcsFile->findExtendedClassName($stackPtr);
667
        if (empty($extends) || is_string($extends) === false) {
668
            return '';
669
        }
670
671
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
672
    }
673
674
675
    /**
676
     * Returns the class name for the static usage of a class.
677
     * This can be a call to a method, the use of a property or constant.
678
     *
679
     * Returns an empty string if the class name could not be reliably inferred.
680
     *
681
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
682
     * @param int                  $stackPtr  The position of a T_NEW token.
683
     *
684
     * @return string
685
     */
686
    public function getFQClassNameFromDoubleColonToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
687
    {
688
        $tokens = $phpcsFile->getTokens();
689
690
        // Check for the existence of the token.
691
        if (isset($tokens[$stackPtr]) === false) {
692
            return '';
693
        }
694
695
        if ($tokens[$stackPtr]['code'] !== T_DOUBLE_COLON) {
696
            return '';
697
        }
698
699
        // Nothing to do if previous token is a variable as we don't know where it was defined.
700
        if ($tokens[$stackPtr - 1]['code'] === T_VARIABLE) {
701
            return '';
702
        }
703
704
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
705
        if (in_array($tokens[$stackPtr - 1]['code'], array(T_PARENT, T_STATIC), true)) {
706
            return '';
707
        }
708
709
        // Get the classname from the class declaration if self is used.
710
        if ($tokens[$stackPtr - 1]['code'] === T_SELF) {
711
            $classDeclarationPtr = $phpcsFile->findPrevious(T_CLASS, $stackPtr - 1);
712
            if ($classDeclarationPtr === false) {
713
                return '';
714
            }
715
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
716
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
717
        }
718
719
        $find = array(
720
                 T_NS_SEPARATOR,
721
                 T_STRING,
722
                 T_NAMESPACE,
723
                 T_WHITESPACE,
724
                );
725
726
        $start     = ($phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true) + 1);
727
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
728
        $className = trim($className);
729
730
        return $this->getFQName($phpcsFile, $stackPtr, $className);
731
    }
732
733
734
    /**
735
     * Get the Fully Qualified name for a class/function/constant etc.
736
     *
737
     * Checks if a class/function/constant name is already fully qualified and
738
     * if not, enrich it with the relevant namespace information.
739
     *
740
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
741
     * @param int                  $stackPtr  The position of the token.
742
     * @param string               $name      The class / function / constant name.
743
     *
744
     * @return string
745
     */
746
    public function getFQName(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $name)
747
    {
748
        if (strpos($name, '\\' ) === 0) {
749
            // Already fully qualified.
750
            return $name;
751
        }
752
753
        // Remove the namespace keyword if used.
754
        if (strpos($name, 'namespace\\') === 0) {
755
            $name = substr($name, 10);
756
        }
757
758
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
759
760
        if ($namespace === '') {
761
            return '\\' . $name;
762
        }
763
        else {
764
            return '\\' . $namespace . '\\' . $name;
765
        }
766
    }
767
768
769
    /**
770
     * Is the class/function/constant name namespaced or global ?
771
     *
772
     * @param string $FQName Fully Qualified name of a class, function etc.
773
     *                       I.e. should always start with a `\` !
774
     *
775
     * @return bool True if namespaced, false if global.
776
     */
777
    public function isNamespaced($FQName) {
778
        if (strpos($FQName, '\\') !== 0) {
779
            throw new PHP_CodeSniffer_Exception('$FQName must be a fully qualified name');
780
        }
781
782
        return (strpos(substr($FQName, 1), '\\') !== false);
783
    }
784
785
786
    /**
787
     * Determine the namespace name an arbitrary token lives in.
788
     *
789
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
790
     * @param int                  $stackPtr  The token position for which to determine the namespace.
791
     *
792
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
793
     */
794
    public function determineNamespace(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
795
    {
796
        $tokens = $phpcsFile->getTokens();
797
798
        // Check for the existence of the token.
799
        if (isset($tokens[$stackPtr]) === false) {
800
            return '';
801
        }
802
803
        // Check for scoped namespace {}.
804
        if (empty($tokens[$stackPtr]['conditions']) === false) {
805
            foreach ($tokens[$stackPtr]['conditions'] as $pointer => $type) {
806
                if ($type === T_NAMESPACE) {
807
                    $namespace = $this->getDeclaredNamespaceName($phpcsFile, $pointer);
808
                    if ($namespace !== false) {
809
                        return $namespace;
810
                    }
811
                    break; // Nested namespaces is not possible.
812
                }
813
            }
814
        }
815
816
        /*
817
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
818
         * Keeping in mind that:
819
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
820
         * - the namespace keyword can also be used as part of a function/method call and such.
821
         * - that a non-named namespace resolves to the global namespace.
822
         */
823
        $previousNSToken = $stackPtr;
824
        $namespace       = false;
825
        do {
826
            $previousNSToken = $phpcsFile->findPrevious(T_NAMESPACE, $previousNSToken -1);
827
828
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
829
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] = $previousNSToken) {
830
                break;
831
            }
832
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
833
834
        } while ($namespace === false && $previousNSToken !== false);
835
836
        // If we still haven't got a namespace, return an empty string.
837
        if ($namespace === false) {
838
            return '';
839
        }
840
        else {
841
            return $namespace;
842
        }
843
    }
844
845
    /**
846
     * Get the complete namespace name for a namespace declaration.
847
     *
848
     * For hierarchical namespaces, the name will be composed of several tokens,
849
     * i.e. MyProject\Sub\Level which will be returned together as one string.
850
     *
851
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
852
     * @param int|bool             $stackPtr  The position of a T_NAMESPACE token.
853
     *
854
     * @return string|false Namespace name or false if not a namespace declaration.
855
     *                      Namespace name can be an empty string for global namespace declaration.
856
     */
857
    public function getDeclaredNamespaceName(PHP_CodeSniffer_File $phpcsFile, $stackPtr )
858
    {
859
        $tokens = $phpcsFile->getTokens();
860
861
        // Check for the existence of the token.
862
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
863
            return false;
864
        }
865
866
        if ($tokens[$stackPtr]['code'] !== T_NAMESPACE) {
867
            return false;
868
        }
869
870
        if ($tokens[$stackPtr + 1]['code'] === T_NS_SEPARATOR) {
871
            // Not a namespace declaration, but use of, i.e. namespace\someFunction();
872
            return false;
873
        }
874
875
        $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
876
        if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) {
877
            // Declaration for global namespace when using multiple namespaces in a file.
878
            // I.e.: namespace {}
879
            return '';
880
        }
881
882
        // Ok, this should be a namespace declaration, so get all the parts together.
883
        $validTokens = array(
884
                        T_STRING,
885
                        T_NS_SEPARATOR,
886
                        T_WHITESPACE,
887
                       );
888
889
        $namespaceName = '';
890
        while(in_array($tokens[$nextToken]['code'], $validTokens, true) === true) {
891
            $namespaceName .= trim($tokens[$nextToken]['content']);
892
            $nextToken++;
893
        }
894
895
        return $namespaceName;
896
    }
897
898
899
    /**
900
     * Returns the method parameters for the specified T_FUNCTION token.
901
     *
902
     * Each parameter is in the following format:
903
     *
904
     * <code>
905
     *   0 => array(
906
     *         'name'              => '$var',  // The variable name.
907
     *         'pass_by_reference' => false,   // Passed by reference.
908
     *         'type_hint'         => string,  // Type hint for array or custom type
909
     *         'nullable_type'     => bool,    // Whether the type given in the type hint is nullable
910
     *         'type_hint'         => string,  // Type hint for array or custom type
911
     *         'raw'               => string,  // Raw content of the tokens for the parameter
912
     *        )
913
     * </code>
914
     *
915
     * Parameters with default values have an additional array index of
916
     * 'default' with the value of the default as a string.
917
     *
918
     * {@internal Duplicate of same method as contained in the `PHP_CodeSniffer_File`
919
     * class, but with some improvements which will probably be introduced in
920
     * PHPCS 2.7.1/2.8. {@see https://github.com/squizlabs/PHP_CodeSniffer/pull/1117}
921
     * and {@see https://github.com/squizlabs/PHP_CodeSniffer/pull/1193}
922
     *
923
     * Once the minimum supported PHPCS version for this sniff library goes beyond
924
     * that, this method can be removed and calls to it replaced with
925
     * `$phpcsFile->getMethodParameters($stackPtr)` calls.
926
     *
927
     * Last synced with PHPCS version: PHPCS 2.7.}}
928
     *
929
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
930
     * @param int                  $stackPtr  The position in the stack of the
931
     *                                        T_FUNCTION token to acquire the
932
     *                                        parameters for.
933
     *
934
     * @return array|false
935
     * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of
936
     *                                   type T_FUNCTION.
937
     */
938
    public function getMethodParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
939
    {
940
        $tokens = $phpcsFile->getTokens();
941
942
        // Check for the existence of the token.
943
        if (isset($tokens[$stackPtr]) === false) {
944
            return false;
945
        }
946
947
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION) {
948
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
949
        }
950
951
        $opener = $tokens[$stackPtr]['parenthesis_opener'];
952
        $closer = $tokens[$stackPtr]['parenthesis_closer'];
953
954
        $vars            = array();
955
        $currVar         = null;
956
        $paramStart      = ($opener + 1);
957
        $defaultStart    = null;
958
        $paramCount      = 0;
959
        $passByReference = false;
960
        $variableLength  = false;
961
        $typeHint        = '';
962
        $nullableType    = false;
963
964
        for ($i = $paramStart; $i <= $closer; $i++) {
965
            // Check to see if this token has a parenthesis or bracket opener. If it does
966
            // it's likely to be an array which might have arguments in it. This
967
            // could cause problems in our parsing below, so lets just skip to the
968
            // end of it.
969 View Code Duplication
            if (isset($tokens[$i]['parenthesis_opener']) === true) {
970
                // Don't do this if it's the close parenthesis for the method.
971
                if ($i !== $tokens[$i]['parenthesis_closer']) {
972
                    $i = ($tokens[$i]['parenthesis_closer'] + 1);
973
                }
974
            }
975
976 View Code Duplication
            if (isset($tokens[$i]['bracket_opener']) === true) {
977
                // Don't do this if it's the close parenthesis for the method.
978
                if ($i !== $tokens[$i]['bracket_closer']) {
979
                    $i = ($tokens[$i]['bracket_closer'] + 1);
980
                }
981
            }
982
983
            switch ($tokens[$i]['code']) {
984
            case T_BITWISE_AND:
985
                $passByReference = true;
986
                break;
987
            case T_VARIABLE:
988
                $currVar = $i;
989
                break;
990
            case T_ELLIPSIS:
991
                $variableLength = true;
992
                break;
993
            case T_ARRAY_HINT:
994
            case T_CALLABLE:
995
                $typeHint = $tokens[$i]['content'];
996
                break;
997
            case T_SELF:
998
            case T_PARENT:
999 View Code Duplication
            case T_STATIC:
1000
                // Self is valid, the others invalid, but were probably intended as type hints.
1001
                if ($defaultStart === null) {
1002
                    $typeHint = $tokens[$i]['content'];
1003
                }
1004
                break;
1005
            case T_STRING:
1006
                // This is a string, so it may be a type hint, but it could
1007
                // also be a constant used as a default value.
1008
                $prevComma = false;
1009 View Code Duplication
                for ($t = $i; $t >= $opener; $t--) {
1010
                    if ($tokens[$t]['code'] === T_COMMA) {
1011
                        $prevComma = $t;
1012
                        break;
1013
                    }
1014
                }
1015
1016
                if ($prevComma !== false) {
1017
                    $nextEquals = false;
1018 View Code Duplication
                    for ($t = $prevComma; $t < $i; $t++) {
1019
                        if ($tokens[$t]['code'] === T_EQUAL) {
1020
                            $nextEquals = $t;
1021
                            break;
1022
                        }
1023
                    }
1024
1025
                    if ($nextEquals !== false) {
1026
                        break;
1027
                    }
1028
                }
1029
1030
                if ($defaultStart === null) {
1031
                    $typeHint .= $tokens[$i]['content'];
1032
                }
1033
                break;
1034 View Code Duplication
            case T_NS_SEPARATOR:
1035
                // Part of a type hint or default value.
1036
                if ($defaultStart === null) {
1037
                    $typeHint .= $tokens[$i]['content'];
1038
                }
1039
                break;
1040
            case T_INLINE_THEN:
1041
                if ($defaultStart === null) {
1042
                    $nullableType = true;
1043
                    $typeHint    .= $tokens[$i]['content'];
1044
                }
1045
                break;
1046
            case T_CLOSE_PARENTHESIS:
1047
            case T_COMMA:
1048
                // If it's null, then there must be no parameters for this
1049
                // method.
1050
                if ($currVar === null) {
1051
                    continue;
1052
                }
1053
1054
                $vars[$paramCount]         = array();
1055
                $vars[$paramCount]['name'] = $tokens[$currVar]['content'];
1056
1057
                if ($defaultStart !== null) {
1058
                    $vars[$paramCount]['default']
1059
                        = $phpcsFile->getTokensAsString(
1060
                            $defaultStart,
1061
                            ($i - $defaultStart)
1062
                        );
1063
                }
1064
1065
                $rawContent = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart)));
1066
1067
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1068
                $vars[$paramCount]['variable_length']   = $variableLength;
1069
                $vars[$paramCount]['type_hint']         = $typeHint;
1070
                $vars[$paramCount]['nullable_type']     = $nullableType;
1071
                $vars[$paramCount]['raw']               = $rawContent;
1072
1073
                // Reset the vars, as we are about to process the next parameter.
1074
                $defaultStart    = null;
1075
                $paramStart      = ($i + 1);
1076
                $passByReference = false;
1077
                $variableLength  = false;
1078
                $typeHint        = '';
1079
                $nullableType    = false;
1080
1081
                $paramCount++;
1082
                break;
1083
            case T_EQUAL:
1084
                $defaultStart = ($i + 1);
1085
                break;
1086
            }//end switch
1087
        }//end for
1088
1089
        return $vars;
1090
1091
    }//end getMethodParameters()
1092
1093
1094
    /**
1095
     * Get the hash algorithm name from the parameter in a hash function call.
1096
     *
1097
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1098
     * @param int                  $stackPtr  The position of the T_STRING function token.
1099
     *
1100
     * @return string|false The algorithm name without quotes if this was a relevant hash
1101
     *                      function call or false if it was not.
1102
     */
1103
    public function getHashAlgorithmParameter(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1104
    {
1105
        $tokens = $phpcsFile->getTokens();
1106
1107
        // Check for the existence of the token.
1108
        if (isset($tokens[$stackPtr]) === false) {
1109
            return false;
1110
        }
1111
1112
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1113
            return false;
1114
        }
1115
1116
        $functionName   = $tokens[$stackPtr]['content'];
1117
        $functionNameLc = strtolower($functionName);
1118
1119
        // Bow out if not one of the functions we're targetting.
1120
        if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
1121
            return false;
1122
        }
1123
1124
        // Get the parameter from the function call which should contain the algorithm name.
1125
        $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
1126
        if ($algoParam === false) {
1127
            return false;
1128
        }
1129
1130
        /**
1131
         * Algorithm is a T_CONSTANT_ENCAPSED_STRING, so we need to remove the quotes.
1132
         */
1133
        $algo = strtolower(trim($algoParam['raw']));
1134
        $algo = $this->stripQuotes($algo);
1135
1136
        return $algo;
1137
    }
1138
1139
}//end class
1140