Completed
Push — bash-completion ( 164352...1454d3 )
by Carsten
86:55 queued 83:59
created

UrlManager::setScriptUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 1
eloc 2
nc 1
nop 1
crap 1
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\Component;
12
use yii\base\InvalidConfigException;
13
use yii\caching\Cache;
14
use yii\helpers\Url;
15
16
/**
17
 * UrlManager handles HTTP request parsing and creation of URLs based on a set of rules.
18
 *
19
 * UrlManager is configured as an application component in [[\yii\base\Application]] by default.
20
 * You can access that instance via `Yii::$app->urlManager`.
21
 *
22
 * You can modify its configuration by adding an array to your application config under `components`
23
 * as it is shown in the following example:
24
 *
25
 * ```php
26
 * 'urlManager' => [
27
 *     'enablePrettyUrl' => true,
28
 *     'rules' => [
29
 *         // your rules go here
30
 *     ],
31
 *     // ...
32
 * ]
33
 * ```
34
 *
35
 * For more details and usage information on UrlManager, see the [guide article on routing](guide:runtime-routing).
36
 *
37
 * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs.
38
 * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by
39
 * [[createAbsoluteUrl()]] to prepend to created URLs.
40
 * @property string $scriptUrl The entry script URL that is used by [[createUrl()]] to prepend to created
41
 * URLs.
42
 *
43
 * @author Qiang Xue <[email protected]>
44
 * @since 2.0
45
 */
46
class UrlManager extends Component
47
{
48
    /**
49
     * @var bool whether to enable pretty URLs. Instead of putting all parameters in the query
50
     * string part of a URL, pretty URLs allow using path info to represent some of the parameters
51
     * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
52
     * "/index.php?r=news%2Fview&id=100".
53
     */
54
    public $enablePrettyUrl = false;
55
    /**
56
     * @var bool whether to enable strict parsing. If strict parsing is enabled, the incoming
57
     * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
58
     * Otherwise, the path info part of the request will be treated as the requested route.
59
     * This property is used only when [[enablePrettyUrl]] is `true`.
60
     */
61
    public $enableStrictParsing = false;
62
    /**
63
     * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is `true`.
64
     * This property is used only if [[enablePrettyUrl]] is `true`. Each element in the array
65
     * is the configuration array for creating a single URL rule. The configuration will
66
     * be merged with [[ruleConfig]] first before it is used for creating the rule object.
67
     *
68
     * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
69
     * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
70
     * array, one can use the key to represent the pattern and the value the corresponding route.
71
     * For example, `'post/<id:\d+>' => 'post/view'`.
72
     *
73
     * For RESTful routing the mentioned shortcut format also allows you to specify the
74
     * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
75
     * You can do that  by prepending it to the pattern, separated by space.
76
     * For example, `'PUT post/<id:\d+>' => 'post/update'`.
77
     * You may specify multiple verbs by separating them with comma
78
     * like this: `'POST,PUT post/index' => 'post/create'`.
79
     * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
80
     * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
81
     * so you normally would not specify a verb for normal GET request.
82
     *
83
     * Here is an example configuration for RESTful CRUD controller:
84
     *
85
     * ```php
86
     * [
87
     *     'dashboard' => 'site/index',
88
     *
89
     *     'POST <controller:[\w-]+>s' => '<controller>/create',
90
     *     '<controller:[\w-]+>s' => '<controller>/index',
91
     *
92
     *     'PUT <controller:[\w-]+>/<id:\d+>'    => '<controller>/update',
93
     *     'DELETE <controller:[\w-]+>/<id:\d+>' => '<controller>/delete',
94
     *     '<controller:[\w-]+>/<id:\d+>'        => '<controller>/view',
95
     * ];
96
     * ```
97
     *
98
     * Note that if you modify this property after the UrlManager object is created, make sure
99
     * you populate the array with rule objects instead of rule configurations.
100
     */
101
    public $rules = [];
102
    /**
103
     * @var string the URL suffix used when [[enablePrettyUrl]] is `true`.
104
     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
105
     * This property is used only if [[enablePrettyUrl]] is `true`.
106
     */
107
    public $suffix;
108
    /**
109
     * @var bool whether to show entry script name in the constructed URL. Defaults to `true`.
110
     * This property is used only if [[enablePrettyUrl]] is `true`.
111
     */
112
    public $showScriptName = true;
113
    /**
114
     * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is `false`.
115
     */
116
    public $routeParam = 'r';
117
    /**
118
     * @var Cache|string the cache object or the application component ID of the cache object.
119
     * Compiled URL rules will be cached through this cache object, if it is available.
120
     *
121
     * After the UrlManager object is created, if you want to change this property,
122
     * you should only assign it with a cache object.
123
     * Set this property to `false` if you do not want to cache the URL rules.
124
     */
125
    public $cache = 'cache';
126
    /**
127
     * @var array the default configuration of URL rules. Individual rule configurations
128
     * specified via [[rules]] will take precedence when the same property of the rule is configured.
129
     */
130
    public $ruleConfig = ['class' => 'yii\web\UrlRule'];
131
    /**
132
     * @var UrlNormalizer|array|string|false the configuration for [[UrlNormalizer]] used by this UrlManager.
133
     * The default value is `false`, which means normalization will be skipped.
134
     * If you wish to enable URL normalization, you should configure this property manually.
135
     * For example:
136
     *
137
     * ```php
138
     * [
139
     *     'class' => 'yii\web\UrlNormalizer',
140
     *     'collapseSlashes' => true,
141
     *     'normalizeTrailingSlash' => true,
142
     * ]
143
     * ```
144
     *
145
     * @since 2.0.10
146
     */
147
    public $normalizer = false;
148
149
    /**
150
     * @var string the cache key for cached rules
151
     * @since 2.0.8
152
     */
153
    protected $cacheKey = __CLASS__;
154
155
    private $_baseUrl;
156
    private $_scriptUrl;
157
    private $_hostInfo;
158
    private $_ruleCache;
159
160
161
    /**
162
     * Initializes UrlManager.
163
     */
164 45
    public function init()
165
    {
166 45
        parent::init();
167
168 45
        if ($this->normalizer !== false) {
169 2
            $this->normalizer = Yii::createObject($this->normalizer);
170 2
            if (!$this->normalizer instanceof UrlNormalizer) {
171
                throw new InvalidConfigException('`' . get_class($this) . '::normalizer` should be an instance of `' . UrlNormalizer::className() . '` or its DI compatible configuration.');
172
            }
173 2
        }
174
175 45
        if (!$this->enablePrettyUrl || empty($this->rules)) {
176 38
            return;
177
        }
178 9
        if (is_string($this->cache)) {
179 1
            $this->cache = Yii::$app->get($this->cache, false);
180 1
        }
181 9
        if ($this->cache instanceof Cache) {
182
            $cacheKey = $this->cacheKey;
183
            $hash = md5(json_encode($this->rules));
184
            if (($data = $this->cache->get($cacheKey)) !== false && isset($data[1]) && $data[1] === $hash) {
185
                $this->rules = $data[0];
186
            } else {
187
                $this->rules = $this->buildRules($this->rules);
188
                $this->cache->set($cacheKey, [$this->rules, $hash]);
189
            }
190
        } else {
191 9
            $this->rules = $this->buildRules($this->rules);
192
        }
193 9
    }
194
195
    /**
196
     * Adds additional URL rules.
197
     *
198
     * This method will call [[buildRules()]] to parse the given rule declarations and then append or insert
199
     * them to the existing [[rules]].
200
     *
201
     * Note that if [[enablePrettyUrl]] is `false`, this method will do nothing.
202
     *
203
     * @param array $rules the new rules to be added. Each array element represents a single rule declaration.
204
     * Please refer to [[rules]] for the acceptable rule format.
205
     * @param bool $append whether to add the new rules by appending them to the end of the existing rules.
206
     */
207
    public function addRules($rules, $append = true)
208
    {
209
        if (!$this->enablePrettyUrl) {
210
            return;
211
        }
212
        $rules = $this->buildRules($rules);
213
        if ($append) {
214
            $this->rules = array_merge($this->rules, $rules);
215
        } else {
216
            $this->rules = array_merge($rules, $this->rules);
217
        }
218
    }
219
220
    /**
221
     * Builds URL rule objects from the given rule declarations.
222
     * @param array $rules the rule declarations. Each array element represents a single rule declaration.
223
     * Please refer to [[rules]] for the acceptable rule formats.
224
     * @return UrlRuleInterface[] the rule objects built from the given rule declarations
225
     * @throws InvalidConfigException if a rule declaration is invalid
226
     */
227 9
    protected function buildRules($rules)
228
    {
229 9
        $compiledRules = [];
230 9
        $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
231 9
        foreach ($rules as $key => $rule) {
232 9
            if (is_string($rule)) {
233 8
                $rule = ['route' => $rule];
234 8
                if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) {
235 1
                    $rule['verb'] = explode(',', $matches[1]);
236
                    // rules that do not apply for GET requests should not be use to create urls
237 1
                    if (!in_array('GET', $rule['verb'])) {
238 1
                        $rule['mode'] = UrlRule::PARSING_ONLY;
239 1
                    }
240 1
                    $key = $matches[4];
241 1
                }
242 8
                $rule['pattern'] = $key;
243 8
            }
244 9
            if (is_array($rule)) {
245 9
                $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
246 9
            }
247 9
            if (!$rule instanceof UrlRuleInterface) {
248
                throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
249
            }
250 9
            $compiledRules[] = $rule;
251 9
        }
252 9
        return $compiledRules;
253
    }
254
255
    /**
256
     * Parses the user request.
257
     * @param Request $request the request component
258
     * @return array|bool the route and the associated parameters. The latter is always empty
259
     * if [[enablePrettyUrl]] is `false`. `false` is returned if the current request cannot be successfully parsed.
260
     */
261 3
    public function parseRequest($request)
262
    {
263 3
        if ($this->enablePrettyUrl) {
264
            /* @var $rule UrlRule */
265 3
            foreach ($this->rules as $rule) {
266 3
                if (($result = $rule->parseRequest($this, $request)) !== false) {
267 3
                    return $result;
268
                }
269 3
            }
270
271 1
            if ($this->enableStrictParsing) {
272 1
                return false;
273
            }
274
275 1
            Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
276
277 1
            $suffix = (string) $this->suffix;
278 1
            $pathInfo = $request->getPathInfo();
279 1
            $normalized = false;
280 1
            if ($this->normalizer !== false) {
281 1
                $pathInfo = $this->normalizer->normalizePathInfo($pathInfo, $suffix, $normalized);
282 1
            }
283 1
            if ($suffix !== '' && $pathInfo !== '') {
284 1
                $n = strlen($this->suffix);
285 1
                if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) {
286 1
                    $pathInfo = substr($pathInfo, 0, -$n);
287 1
                    if ($pathInfo === '') {
288
                        // suffix alone is not allowed
289
                        return false;
290
                    }
291 1
                } else {
292
                    // suffix doesn't match
293 1
                    return false;
294
                }
295 1
            }
296
297 1
            if ($normalized) {
298
                // pathInfo was changed by normalizer - we need also normalize route
299 1
                return $this->normalizer->normalizeRoute([$pathInfo, []]);
300
            } else {
301 1
                return [$pathInfo, []];
302
            }
303
        } else {
304 1
            Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
305 1
            $route = $request->getQueryParam($this->routeParam, '');
306 1
            if (is_array($route)) {
307 1
                $route = '';
308 1
            }
309
310 1
            return [(string) $route, []];
311
        }
312
    }
313
314
    /**
315
     * Creates a URL using the given route and query parameters.
316
     *
317
     * You may specify the route as a string, e.g., `site/index`. You may also use an array
318
     * if you want to specify additional query parameters for the URL being created. The
319
     * array format must be:
320
     *
321
     * ```php
322
     * // generates: /index.php?r=site%2Findex&param1=value1&param2=value2
323
     * ['site/index', 'param1' => 'value1', 'param2' => 'value2']
324
     * ```
325
     *
326
     * If you want to create a URL with an anchor, you can use the array format with a `#` parameter.
327
     * For example,
328
     *
329
     * ```php
330
     * // generates: /index.php?r=site%2Findex&param1=value1#name
331
     * ['site/index', 'param1' => 'value1', '#' => 'name']
332
     * ```
333
     *
334
     * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
335
     *
336
     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
337
     * as an absolute route.
338
     *
339
     * @param string|array $params use a string to represent a route (e.g. `site/index`),
340
     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
341
     * @return string the created URL
342
     */
343 32
    public function createUrl($params)
344
    {
345 32
        $params = (array) $params;
346 32
        $anchor = isset($params['#']) ? '#' . $params['#'] : '';
347 32
        unset($params['#'], $params[$this->routeParam]);
348
349 32
        $route = trim($params[0], '/');
350 32
        unset($params[0]);
351
352 32
        $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();
353
354 32
        if ($this->enablePrettyUrl) {
355 7
            $cacheKey = $route . '?';
356 7
            foreach ($params as $key => $value) {
357 5
                if ($value !== null) {
358 5
                    $cacheKey .= $key . '&';
359 5
                }
360 7
            }
361
362 7
            $url = $this->getUrlFromCache($cacheKey, $route, $params);
363
364 7
            if ($url === false) {
365 7
                $cacheable = true;
366 7
                foreach ($this->rules as $rule) {
367
                    /* @var $rule UrlRule */
368 7
                    if (!empty($rule->defaults) && $rule->mode !== UrlRule::PARSING_ONLY) {
369
                        // if there is a rule with default values involved, the matching result may not be cached
370 1
                        $cacheable = false;
371 1
                    }
372 7
                    if (($url = $rule->createUrl($this, $route, $params)) !== false) {
373 6
                        if ($cacheable) {
374 6
                            $this->setRuleToCache($cacheKey, $rule);
375 6
                        }
376 6
                        break;
377
                    }
378 7
                }
379 7
            }
380
381 7
            if ($url !== false) {
382 6
                if (strpos($url, '://') !== false) {
383 3
                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
384 2
                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
385
                    } else {
386 1
                        return $url . $baseUrl . $anchor;
387
                    }
388
                } else {
389 4
                    return "$baseUrl/{$url}{$anchor}";
390
                }
391
            }
392
393 2
            if ($this->suffix !== null) {
394 1
                $route .= $this->suffix;
395 1
            }
396 2
            if (!empty($params) && ($query = http_build_query($params)) !== '') {
397 2
                $route .= '?' . $query;
398 2
            }
399
400 2
            return "$baseUrl/{$route}{$anchor}";
401
        } else {
402 26
            $url = "$baseUrl?{$this->routeParam}=" . urlencode($route);
403 26
            if (!empty($params) && ($query = http_build_query($params)) !== '') {
404 24
                $url .= '&' . $query;
405 24
            }
406
407 26
            return $url . $anchor;
408
        }
409
    }
410
411
    /**
412
     * Get URL from internal cache if exists
413
     * @param string $cacheKey generated cache key to store data.
414
     * @param string $route the route (e.g. `site/index`).
415
     * @param array $params rule params.
416
     * @return bool|string the created URL
417
     * @see createUrl()
418
     * @since 2.0.8
419
     */
420 7
    protected function getUrlFromCache($cacheKey, $route, $params)
421
    {
422 7
        if (!empty($this->_ruleCache[$cacheKey])) {
423 4
            foreach ($this->_ruleCache[$cacheKey] as $rule) {
424
                /* @var $rule UrlRule */
425 4
                if (($url = $rule->createUrl($this, $route, $params)) !== false) {
426 4
                    return $url;
427
                }
428
            }
429
        } else {
430 7
            $this->_ruleCache[$cacheKey] = [];
431
        }
432 7
        return false;
433
    }
434
435
    /**
436
     * Store rule (e.g. [[UrlRule]]) to internal cache
437
     * @param $cacheKey
438
     * @param UrlRuleInterface $rule
439
     * @since 2.0.8
440
     */
441 6
    protected function setRuleToCache($cacheKey, UrlRuleInterface $rule)
442
    {
443 6
        $this->_ruleCache[$cacheKey][] = $rule;
444 6
    }
445
446
    /**
447
     * Creates an absolute URL using the given route and query parameters.
448
     *
449
     * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
450
     *
451
     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
452
     * as an absolute route.
453
     *
454
     * @param string|array $params use a string to represent a route (e.g. `site/index`),
455
     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
456
     * @param string|null $scheme the scheme to use for the URL (either `http`, `https` or empty string
457
     * for protocol-relative URL).
458
     * If not specified the scheme of the current request will be used.
459
     * @return string the created URL
460
     * @see createUrl()
461
     */
462 13
    public function createAbsoluteUrl($params, $scheme = null)
463
    {
464 13
        $params = (array) $params;
465 13
        $url = $this->createUrl($params);
466 13
        if (strpos($url, '://') === false) {
467 11
            $url = $this->getHostInfo() . $url;
468 11
        }
469
470 13
        return Url::ensureScheme($url, $scheme);
471
    }
472
473
    /**
474
     * Returns the base URL that is used by [[createUrl()]] to prepend to created URLs.
475
     * It defaults to [[Request::baseUrl]].
476
     * This is mainly used when [[enablePrettyUrl]] is `true` and [[showScriptName]] is `false`.
477
     * @return string the base URL that is used by [[createUrl()]] to prepend to created URLs.
478
     * @throws InvalidConfigException if running in console application and [[baseUrl]] is not configured.
479
     */
480 5
    public function getBaseUrl()
481
    {
482 5
        if ($this->_baseUrl === null) {
483 1
            $request = Yii::$app->getRequest();
484 1
            if ($request instanceof Request) {
485 1
                $this->_baseUrl = $request->getBaseUrl();
486 1
            } else {
487
                throw new InvalidConfigException('Please configure UrlManager::baseUrl correctly as you are running a console application.');
488
            }
489 1
        }
490
491 5
        return $this->_baseUrl;
492
    }
493
494
    /**
495
     * Sets the base URL that is used by [[createUrl()]] to prepend to created URLs.
496
     * This is mainly used when [[enablePrettyUrl]] is `true` and [[showScriptName]] is `false`.
497
     * @param string $value the base URL that is used by [[createUrl()]] to prepend to created URLs.
498
     */
499 13
    public function setBaseUrl($value)
500
    {
501 13
        $this->_baseUrl = $value === null ? null : rtrim($value, '/');
502 13
    }
503
504
    /**
505
     * Returns the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
506
     * It defaults to [[Request::scriptUrl]].
507
     * This is mainly used when [[enablePrettyUrl]] is `false` or [[showScriptName]] is `true`.
508
     * @return string the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
509
     * @throws InvalidConfigException if running in console application and [[scriptUrl]] is not configured.
510
     */
511 29
    public function getScriptUrl()
512
    {
513 29
        if ($this->_scriptUrl === null) {
514 10
            $request = Yii::$app->getRequest();
515 10
            if ($request instanceof Request) {
516 10
                $this->_scriptUrl = $request->getScriptUrl();
517 10
            } else {
518
                throw new InvalidConfigException('Please configure UrlManager::scriptUrl correctly as you are running a console application.');
519
            }
520 10
        }
521
522 29
        return $this->_scriptUrl;
523
    }
524
525
    /**
526
     * Sets the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
527
     * This is mainly used when [[enablePrettyUrl]] is `false` or [[showScriptName]] is `true`.
528
     * @param string $value the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
529
     */
530 23
    public function setScriptUrl($value)
531
    {
532 23
        $this->_scriptUrl = $value;
533 23
    }
534
535
    /**
536
     * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
537
     * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
538
     * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured.
539
     */
540 13
    public function getHostInfo()
541
    {
542 13
        if ($this->_hostInfo === null) {
543 7
            $request = Yii::$app->getRequest();
544 7
            if ($request instanceof \yii\web\Request) {
545 7
                $this->_hostInfo = $request->getHostInfo();
546 7
            } else {
547
                throw new InvalidConfigException('Please configure UrlManager::hostInfo correctly as you are running a console application.');
548
            }
549 7
        }
550
551 13
        return $this->_hostInfo;
552
    }
553
554
    /**
555
     * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
556
     * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
557
     */
558 9
    public function setHostInfo($value)
559
    {
560 9
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
561 9
    }
562
}
563