GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 533da6...33cff4 )
by Robert
08:57
created

UrlRule::preparePattern()   D

Complexity

Conditions 10
Paths 13

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 10.0071

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 23
cts 24
cp 0.9583
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 26
nc 13
nop 0
crap 10.0071

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\base\Object;
13
14
/**
15
 * UrlRule represents a rule used by [[UrlManager]] for parsing and generating URLs.
16
 *
17
 * To define your own URL parsing and creation logic you can extend from this class
18
 * and add it to [[UrlManager::rules]] like this:
19
 *
20
 * ```php
21
 * 'rules' => [
22
 *     ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...],
23
 *     // ...
24
 * ]
25
 * ```
26
 *
27
 * @author Qiang Xue <[email protected]>
28
 * @since 2.0
29
 */
30
class UrlRule extends Object implements UrlRuleInterface
31
{
32
    /**
33
     * Set [[mode]] with this value to mark that this rule is for URL parsing only
34
     */
35
    const PARSING_ONLY = 1;
36
    /**
37
     * Set [[mode]] with this value to mark that this rule is for URL creation only
38
     */
39
    const CREATION_ONLY = 2;
40
41
    /**
42
     * @var string the name of this rule. If not set, it will use [[pattern]] as the name.
43
     */
44
    public $name;
45
    /**
46
     * On the rule initialization, the [[pattern]] matching parameters names will be replaced with [[placeholders]].
47
     * @var string the pattern used to parse and create the path info part of a URL.
48
     * @see host
49
     * @see placeholders
50
     */
51
    public $pattern;
52
    /**
53
     * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`).
54
     * @see pattern
55
     */
56
    public $host;
57
    /**
58
     * @var string the route to the controller action
59
     */
60
    public $route;
61
    /**
62
     * @var array the default GET parameters (name => value) that this rule provides.
63
     * When this rule is used to parse the incoming request, the values declared in this property
64
     * will be injected into $_GET.
65
     */
66
    public $defaults = [];
67
    /**
68
     * @var string the URL suffix used for this rule.
69
     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
70
     * If not set, the value of [[UrlManager::suffix]] will be used.
71
     */
72
    public $suffix;
73
    /**
74
     * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
75
     * Use array to represent multiple verbs that this rule may match.
76
     * If this property is not set, the rule can match any verb.
77
     * Note that this property is only used when parsing a request. It is ignored for URL creation.
78
     */
79
    public $verb;
80
    /**
81
     * @var int a value indicating if this rule should be used for both request parsing and URL creation,
82
     * parsing only, or creation only.
83
     * If not set or 0, it means the rule is both request parsing and URL creation.
84
     * If it is [[PARSING_ONLY]], the rule is for request parsing only.
85
     * If it is [[CREATION_ONLY]], the rule is for URL creation only.
86
     */
87
    public $mode;
88
    /**
89
     * @var bool a value indicating if parameters should be url encoded.
90
     */
91
    public $encodeParams = true;
92
    /**
93
     * @var UrlNormalizer|array|false|null the configuration for [[UrlNormalizer]] used by this rule.
94
     * If `null`, [[UrlManager::normalizer]] will be used, if `false`, normalization will be skipped
95
     * for this rule.
96
     * @since 2.0.10
97
     */
98
    public $normalizer;
99
100
    /**
101
     * @var array list of placeholders for matching parameters names. Used in [[parseRequest()]], [[createUrl()]].
102
     * On the rule initialization, the [[pattern]] parameters names will be replaced with placeholders.
103
     * This array contains relations between the original parameters names and their placeholders.
104
     * The array keys are the placeholders and the values are the original names.
105
     *
106
     * @see parseRequest()
107
     * @see createUrl()
108
     * @since 2.0.7
109
     */
110
    protected $placeholders = [];
111
112
    /**
113
     * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
114
     */
115
    private $_template;
116
    /**
117
     * @var string the regex for matching the route part. This is used in generating URL.
118
     */
119
    private $_routeRule;
120
    /**
121
     * @var array list of regex for matching parameters. This is used in generating URL.
122
     */
123
    private $_paramRules = [];
124
    /**
125
     * @var array list of parameters used in the route.
126
     */
127
    private $_routeParams = [];
128
129
130
    /**
131
     * @return string
132
     * @since 2.0.11
133
     */
134 12
    public function __toString()
135
    {
136 12
        $str = '';
137 12
        if ($this->verb !== null) {
138 3
            $str .= implode(',', $this->verb) . ' ';
139
        }
140 12
        if ($this->host !== null && strrpos($this->name, $this->host) === false) {
141 1
            $str .= $this->host . '/';
142
        }
143 12
        $str .= $this->name;
144
145 12
        if ($str === '') {
146 1
            return '/';
147
        }
148 12
        return $str;
149
    }
150
151
    /**
152
     * Initializes this rule.
153
     */
154 87
    public function init()
155
    {
156 87
        if ($this->pattern === null) {
157
            throw new InvalidConfigException('UrlRule::pattern must be set.');
158
        }
159 87
        if ($this->route === null) {
160
            throw new InvalidConfigException('UrlRule::route must be set.');
161
        }
162 87
        if (is_array($this->normalizer)) {
163 1
            $normalizerConfig = array_merge(['class' => UrlNormalizer::className()], $this->normalizer);
164 1
            $this->normalizer = Yii::createObject($normalizerConfig);
165
        }
166 87
        if ($this->normalizer !== null && $this->normalizer !== false && !$this->normalizer instanceof UrlNormalizer) {
167
            throw new InvalidConfigException('Invalid config for UrlRule::normalizer.');
168
        }
169 87
        if ($this->verb !== null) {
170 11
            if (is_array($this->verb)) {
171 11
                foreach ($this->verb as $i => $verb) {
172 11
                    $this->verb[$i] = strtoupper($verb);
173
                }
174
            } else {
175
                $this->verb = [strtoupper($this->verb)];
176
            }
177
        }
178 87
        if ($this->name === null) {
179 87
            $this->name = $this->pattern;
180
        }
181
182 87
        $this->preparePattern();
183 87
    }
184
185
    /**
186
     * Process [[$pattern]] on rule initialization.
187
     */
188 87
    private function preparePattern()
189
    {
190 87
        $this->pattern = $this->trimSlashes($this->pattern);
191 87
        $this->route = trim($this->route, '/');
192
193 87
        if ($this->host !== null) {
194 17
            $this->host = rtrim($this->host, '/');
195 17
            $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');
196 83
        } elseif ($this->pattern === '') {
197 21
            $this->_template = '';
198 21
            $this->pattern = '#^$#u';
199
200 21
            return;
201 75
        } elseif (($pos = strpos($this->pattern, '://')) !== false) {
202 9
            if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
203 9
                $this->host = substr($this->pattern, 0, $pos2);
204
            } else {
205
                $this->host = $this->pattern;
206
            }
207 71
        } elseif (strpos($this->pattern, '//') === 0) {
208 8
            if (($pos2 = strpos($this->pattern, '/', $pos + 2)) !== false) {
209 8
                $this->host = substr($this->pattern, 0, $pos2);
210
            } else {
211 4
                $this->host = $this->pattern;
212
            }
213
        } else {
214 67
            $this->pattern = '/' . $this->pattern . '/';
215
        }
216
217 79
        if (strpos($this->route, '<') !== false && preg_match_all('/<([\w._-]+)>/', $this->route, $matches)) {
218 13
            foreach ($matches[1] as $name) {
219 13
                $this->_routeParams[$name] = "<$name>";
220
            }
221
        }
222
223 79
        $this->translatePattern(true);
224 79
    }
225
226
    /**
227
     * Prepares [[$pattern]] on rule initialization - replace parameter names by placeholders.
228
     *
229
     * @param bool $allowAppendSlash Defines position of slash in the param pattern in [[$pattern]].
230
     * If `false` slash will be placed at the beginning of param pattern. If `true` slash position will be detected
231
     * depending on non-optional pattern part.
232
     */
233 79
    private function translatePattern($allowAppendSlash)
234
    {
235
        $tr = [
236 79
            '.' => '\\.',
237
            '*' => '\\*',
238
            '$' => '\\$',
239
            '[' => '\\[',
240
            ']' => '\\]',
241
            '(' => '\\(',
242
            ')' => '\\)',
243
        ];
244
245 79
        $tr2 = [];
246 79
        $requiredPatternPart = $this->pattern;
247 79
        $oldOffset = 0;
248 79
        if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
249 77
            $appendSlash = false;
250 77
            foreach ($matches as $match) {
251 77
                $name = $match[1][0];
252 77
                $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
253 77
                $placeholder = 'a' . hash('crc32b', $name); // placeholder must begin with a letter
254 77
                $this->placeholders[$placeholder] = $name;
255 77
                if (array_key_exists($name, $this->defaults)) {
256 13
                    $length = strlen($match[0][0]);
257 13
                    $offset = $match[0][1];
258 13
                    $requiredPatternPart = str_replace("/{$match[0][0]}/", '//', $requiredPatternPart);
259
                    if (
260 13
                        $allowAppendSlash
261 13
                        && ($appendSlash || $offset === 1)
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $offset (string) and 1 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
262 12
                        && (($offset - $oldOffset) === 1)
263 12
                        && isset($this->pattern[$offset + $length])
264 12
                        && $this->pattern[$offset + $length] === '/'
265 12
                        && isset($this->pattern[$offset + $length + 1])
266
                    ) {
267
                        // if pattern starts from optional params, put slash at the end of param pattern
268
                        // @see https://github.com/yiisoft/yii2/issues/13086
269 4
                        $appendSlash = true;
270 4
                        $tr["<$name>/"] = "((?P<$placeholder>$pattern)/)?";
271
                    } elseif (
272 13
                        $offset > 1
273 5
                        && $this->pattern[$offset - 1] === '/'
274 5
                        && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')
275
                    ) {
276 5
                        $appendSlash = false;
277 5
                        $tr["/<$name>"] = "(/(?P<$placeholder>$pattern))?";
278
                    }
279 13
                    $tr["<$name>"] = "(?P<$placeholder>$pattern)?";
280 13
                    $oldOffset = $offset + $length;
281
                } else {
282 77
                    $appendSlash = false;
283 77
                    $tr["<$name>"] = "(?P<$placeholder>$pattern)";
284
                }
285
286 77
                if (isset($this->_routeParams[$name])) {
287 13
                    $tr2["<$name>"] = "(?P<$placeholder>$pattern)";
288
                } else {
289 77
                    $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#u";
290
                }
291
            }
292
        }
293
294
        // we have only optional params in route - ensure slash position on param patterns
295 79
        if ($allowAppendSlash && trim($requiredPatternPart, '/') === '') {
296 12
            $this->translatePattern(false);
297 12
            return;
298
        }
299
300 79
        $this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern);
301 79
        $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
302
303
        // if host starts with relative scheme, then insert pattern to match any
304 79
        if (strpos($this->host, '//') === 0) {
305 8
            $this->pattern = substr_replace($this->pattern, '[\w]+://', 2, 0);
306
        }
307
308 79
        if (!empty($this->_routeParams)) {
309 13
            $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
310
        }
311 79
    }
312
313
    /**
314
     * @param UrlManager $manager the URL manager
315
     * @return UrlNormalizer|null
316
     * @since 2.0.10
317
     */
318 15
    protected function getNormalizer($manager)
319
    {
320 15
        if ($this->normalizer === null) {
321 14
            return $manager->normalizer;
322
        } else {
323 1
            return $this->normalizer;
324
        }
325
    }
326
327
    /**
328
     * @param UrlManager $manager the URL manager
329
     * @return bool
330
     * @since 2.0.10
331
     */
332 15
    protected function hasNormalizer($manager)
333
    {
334 15
        return $this->getNormalizer($manager) instanceof UrlNormalizer;
335
    }
336
337
    /**
338
     * Parses the given request and returns the corresponding route and parameters.
339
     * @param UrlManager $manager the URL manager
340
     * @param Request $request the request component
341
     * @return array|bool the parsing result. The route and the parameters are returned as an array.
342
     * If `false`, it means this rule cannot be used to parse this path info.
343
     */
344 15
    public function parseRequest($manager, $request)
345
    {
346 15
        if ($this->mode === self::CREATION_ONLY) {
347 3
            return false;
348
        }
349
350 15
        if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
351 2
            return false;
352
        }
353
354 15
        $suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix);
355 15
        $pathInfo = $request->getPathInfo();
356 15
        $normalized = false;
357 15
        if ($this->hasNormalizer($manager)) {
358 4
            $pathInfo = $this->getNormalizer($manager)->normalizePathInfo($pathInfo, $suffix, $normalized);
359
        }
360 15
        if ($suffix !== '' && $pathInfo !== '') {
361 9
            $n = strlen($suffix);
362 9
            if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
363 9
                $pathInfo = substr($pathInfo, 0, -$n);
364 9
                if ($pathInfo === '') {
365
                    // suffix alone is not allowed
366 3
                    return false;
367
                }
368
            } else {
369 8
                return false;
370
            }
371
        }
372
373 15
        if ($this->host !== null) {
374 3
            $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
375
        }
376
377 15
        if (!preg_match($this->pattern, $pathInfo, $matches)) {
378 14
            return false;
379
        }
380 15
        $matches = $this->substitutePlaceholderNames($matches);
381
382 15
        foreach ($this->defaults as $name => $value) {
383 3
            if (!isset($matches[$name]) || $matches[$name] === '') {
384 3
                $matches[$name] = $value;
385
            }
386
        }
387 15
        $params = $this->defaults;
388 15
        $tr = [];
389 15
        foreach ($matches as $name => $value) {
390 15
            if (isset($this->_routeParams[$name])) {
391 3
                $tr[$this->_routeParams[$name]] = $value;
392 3
                unset($params[$name]);
393 15
            } elseif (isset($this->_paramRules[$name])) {
394 14
                $params[$name] = $value;
395
            }
396
        }
397 15
        if ($this->_routeRule !== null) {
398 3
            $route = strtr($this->route, $tr);
399
        } else {
400 15
            $route = $this->route;
401
        }
402
403 15
        Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);
404
405 15
        if ($normalized) {
406
            // pathInfo was changed by normalizer - we need also normalize route
407 4
            return $this->getNormalizer($manager)->normalizeRoute([$route, $params]);
408
        } else {
409 13
            return [$route, $params];
410
        }
411
    }
412
413
    /**
414
     * Creates a URL according to the given route and parameters.
415
     * @param UrlManager $manager the URL manager
416
     * @param string $route the route. It should not have slashes at the beginning or the end.
417
     * @param array $params the parameters
418
     * @return string|bool the created URL, or `false` if this rule cannot be used for creating this URL.
419
     */
420 71
    public function createUrl($manager, $route, $params)
421
    {
422 71
        if ($this->mode === self::PARSING_ONLY) {
423 9
            return false;
424
        }
425
426 71
        $tr = [];
427
428
        // match the route part first
429 71
        if ($route !== $this->route) {
430 59
            if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
431 10
                $matches = $this->substitutePlaceholderNames($matches);
432 10
                foreach ($this->_routeParams as $name => $token) {
433 10
                    if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
434 1
                        $tr[$token] = '';
435
                    } else {
436 10
                        $tr[$token] = $matches[$name];
437
                    }
438
                }
439
            } else {
440 59
                return false;
441
            }
442
        }
443
444
        // match default params
445
        // if a default param is not in the route pattern, its value must also be matched
446 70
        foreach ($this->defaults as $name => $value) {
447 13
            if (isset($this->_routeParams[$name])) {
448 1
                continue;
449
            }
450 13
            if (!isset($params[$name])) {
451
                // allow omit empty optional params
452
                // @see https://github.com/yiisoft/yii2/issues/10970
453 9
                if (in_array($name, $this->placeholders) && strcmp($value, '') === 0) {
454 1
                    $params[$name] = '';
455
                } else {
456 9
                    return false;
457
                }
458
            }
459 13
            if (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
460 13
                unset($params[$name]);
461 13
                if (isset($this->_paramRules[$name])) {
462 9
                    $tr["<$name>"] = '';
463
                }
464 13
            } elseif (!isset($this->_paramRules[$name])) {
465 13
                return false;
466
            }
467
        }
468
469
        // match params in the pattern
470 70
        foreach ($this->_paramRules as $name => $rule) {
471 61
            if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
472 60
                $tr["<$name>"] = $this->encodeParams ? urlencode($params[$name]) : $params[$name];
473 60
                unset($params[$name]);
474 49
            } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
475 49
                return false;
476
            }
477
        }
478
479 70
        $url = $this->trimSlashes(strtr($this->_template, $tr));
480 70
        if ($this->host !== null) {
481 13
            $pos = strpos($url, '/', 8);
482 13
            if ($pos !== false) {
483 13
                $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
484
            }
485 58
        } elseif (strpos($url, '//') !== false) {
486 9
            $url = preg_replace('#/+#', '/', trim($url, '/'));
487
        }
488
489 70
        if ($url !== '') {
490 62
            $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
491
        }
492
493 70
        if (!empty($params) && ($query = http_build_query($params)) !== '') {
494 51
            $url .= '?' . $query;
495
        }
496
497 70
        return $url;
498
    }
499
500
    /**
501
     * Returns list of regex for matching parameter.
502
     * @return array parameter keys and regexp rules.
503
     *
504
     * @since 2.0.6
505
     */
506
    protected function getParamRules()
507
    {
508
        return $this->_paramRules;
509
    }
510
511
    /**
512
     * Iterates over [[placeholders]] and checks whether each placeholder exists as a key in $matches array.
513
     * When found - replaces this placeholder key with a appropriate name of matching parameter.
514
     * Used in [[parseRequest()]], [[createUrl()]].
515
     *
516
     * @param array $matches result of `preg_match()` call
517
     * @return array input array with replaced placeholder keys
518
     * @see placeholders
519
     * @since 2.0.7
520
     */
521 25
    protected function substitutePlaceholderNames(array $matches)
522
    {
523 25
        foreach ($this->placeholders as $placeholder => $name) {
524 24
            if (isset($matches[$placeholder])) {
525 24
                $matches[$name] = $matches[$placeholder];
526 24
                unset($matches[$placeholder]);
527
            }
528
        }
529 25
        return $matches;
530
    }
531
532
    /**
533
     * Trim slashes in passed string. If string begins with '//', two slashes are left as is
534
     * in the beginning of a string.
535
     *
536
     * @param string $string
537
     * @return string
538
     */
539 87
    private function trimSlashes($string)
540
    {
541 87
        if (strpos($string, '//') === 0) {
542 16
            return '//' . trim($string, '/');
543
        }
544 87
        return trim($string, '/');
545
    }
546
}
547