TUrlMappingPattern::getServiceID()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * TUrlMapping, TUrlMappingPattern and TUrlMappingPatternSecureConnection class file.
5
 *
6
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Web;
12
13
use Prado\Collections\TAttributeCollection;
14
use Prado\Exceptions\TConfigurationException;
15
use Prado\Exceptions\TInvalidDataValueException;
16
use Prado\Prado;
17
use Prado\TPropertyValue;
18
19
/**
20
 * TUrlMappingPattern class.
21
 *
22
 * TUrlMappingPattern represents a pattern used to parse and construct URLs.
23
 * If the currently requested URL matches the pattern, it will alter
24
 * the THttpRequest parameters. If a constructUrl() call matches the pattern
25
 * parameters, the pattern will generate a valid URL. In both case, only the PATH_INFO
26
 * part of a URL is parsed/constructed using the pattern.
27
 *
28
 * To specify the pattern, set the {@see setPattern Pattern} property.
29
 * {@see setPattern Pattern} takes a string expression with
30
 * parameter names enclosed between a left brace '{' and a right brace '}'.
31
 * The patterns for each parameter can be set using {@see getParameters Parameters}
32
 * attribute collection. For example
33
 * ```php
34
 * <url ... pattern="articles/{year}/{month}/{day}"
35
 *          parameters.year="\d{4}" parameters.month="\d{2}" parameters.day="\d+" />
36
 * ```
37
 *
38
 * In the above example, the pattern contains 3 parameters named "year",
39
 * "month" and "day". The pattern for these parameters are, respectively,
40
 * "\d{4}" (4 digits), "\d{2}" (2 digits) and "\d+" (1 or more digits).
41
 * Essentially, the <tt>Parameters</tt> attribute name and values are used
42
 * as substrings in replacing the placeholders in the <tt>Pattern</tt> string
43
 * to form a complete regular expression string.
44
 *
45
 * For more complicated patterns, one may specify the pattern using a regular expression
46
 * by {@see setRegularExpression RegularExpression}. For example, the above pattern
47
 * is equivalent to the following regular expression-based pattern:
48
 * ```
49
 * #^articles/(?P<year>\d{4})/(?P<month>\d{2})\/(?P<day>\d+)$#u
50
 * ```
51
 * The above regular expression used the "named group" feature available in PHP.
52
 * If you intended to use the <tt>RegularExpression</tt> property or
53
 * regular expressions in CDATA sections, notice that you need to escape the slash,
54
 * if you are using the slash as regular expressions delimiter.
55
 *
56
 * Thus, only an url that matches the pattern will be valid. For example,
57
 * a URL <tt>http://example.com/index.php/articles/2006/07/21</tt> will match the above pattern,
58
 * while <tt>http://example.com/index.php/articles/2006/07/hello</tt> will not
59
 * since the "day" parameter pattern is not satisfied.
60
 *
61
 * The parameter values are available through the <tt>THttpRequest</tt> instance (e.g.
62
 * <tt>$this->Request['year']</tt>).
63
 *
64
 * The {@see setServiceParameter ServiceParameter} and {@see setServiceID ServiceID}
65
 * (the default ID is 'page') set the service parameter and service id respectively.
66
 *
67
 * Since 3.1.4 you can also use simplyfied wildcard patterns to match multiple
68
 * ServiceParameters with a single rule. The pattern must contain the placeholder
69
 * {*} for the ServiceParameter. For example
70
 *
71
 * <url ServiceParameter="adminpages.*" pattern="admin/{*}" />
72
 *
73
 * This rule will match an URL like <tt>http://example.com/index.php/admin/edituser</tt>
74
 * and resolve it to the page Application.pages.admin.edituser. The wildcard matching
75
 * is non-recursive. That means you have to add a rule for every subdirectory you
76
 * want to access pages in:
77
 *
78
 * <url ServiceParameter="adminpages.users.*" pattern="useradmin/{*}" />
79
 *
80
 * It is still possible to define an explicit rule for a page in the wildcard path.
81
 * This rule has to preceed the wildcard rule.
82
 *
83
 * You can also use parameters with wildcard patterns. The parameters are then
84
 * available with every matching page:
85
 *
86
 * <url ServiceParameter="adminpages.*" pattern="admin/{*}/{id}" parameters.id="\d+" />
87
 *
88
 * To enable automatic parameter encoding in a path format from wildcard patterns you can set
89
 * {@setUrlFormat UrlFormat} to 'Path':
90
 *
91
 * <url ServiceParameter="adminpages.*" pattern="admin/{*}" UrlFormat="Path" />
92
 *
93
 * This will create and parse URLs of the form
94
 * <tt>.../index.php/admin/listuser/param1/value1/param2/value2</tt>.
95
 *
96
 * Use {@setUrlParamSeparator} to define another separator character between parameter
97
 * name and value. Parameter/value pairs are always separated by a '/'.
98
 *
99
 * <url ServiceParameter="adminpages.*" pattern="admin/{*}" UrlFormat="Path" UrlParamSeparator="-" />
100
 *
101
 * <tt>.../index.php/admin/listuser/param1-value1/param2-value2</tt>.
102
 *
103
 * Since 3.2.2 you can also add a list of "constants" parameters that can be used just
104
 * like the original "parameters" parameters, except that the supplied value will be treated
105
 * as a simple string constant instead of a regular expression. For example
106
 *
107
 * <url ServiceParameter="MyPage" pattern="/mypage/mypath/list/detail/{pageidx}" parameters.pageidx="\d+" constants.listtype="detailed"/>
108
 * <url ServiceParameter="MyPage" pattern="/mypage/mypath/list/summary/{pageidx}" parameters.pageidx="\d+" constants.listtype="summarized"/>
109
 *
110
 * These rules, when matched by the actual request, will make the application see a "lisstype" parameter present
111
 * (even through not supplied in the request) and equal to "detailed" or "summarized", depending on the friendly url matched.
112
 * The constants is practically a table-based validation and translation of specified, fixed-set parameter values.
113
 *
114
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
115
 * @since 3.0.5
116
 */
117
class TUrlMappingPattern extends \Prado\TComponent
118
{
119
	/**
120
	 * @var string service parameter such as Page class name.
121
	 */
122
	private $_serviceParameter;
123
	/**
124
	 * @var string service ID, default is 'page'.
125
	 */
126
	private $_serviceID = 'page';
127
	/**
128
	 * @var string url pattern to match.
129
	 */
130
	private $_pattern;
131
	/**
132
	 * @var TAttributeCollection parameter regular expressions.
133
	 */
134
	private $_parameters;
135
	/**
136
	 * @var TAttributeCollection of constant parameters.
137
	 */
138
	protected $_constants;
139
	/**
140
	 * @var string regular expression pattern.
141
	 */
142
	private $_regexp = '';
143
144
	private $_customUrl = true;
145
146
	private $_manager;
147
148
	private $_caseSensitive = true;
149
150
	private $_isWildCardPattern = false;
151
152
	private $_urlFormat = THttpRequestUrlFormat::Get;
153
154
	private $_separator = '/';
155
156
	/**
157
	 * @var TUrlMappingPatternSecureConnection
158
	 * @since 3.2
159
	 */
160
	private $_secureConnection = TUrlMappingPatternSecureConnection::Automatic;
161
162
	/**
163
	 * Constructor.
164
	 * @param TUrlManager $manager the URL manager instance
165
	 */
166
	public function __construct(TUrlManager $manager)
167 1
	{
168
		$this->_manager = $manager;
169 1
		parent::__construct();
170 1
	}
171
172
	/**
173
	 * @return TUrlManager the URL manager instance
174
	 */
175
	public function getManager()
176
	{
177
		return $this->_manager;
178
	}
179
180
	/**
181
	 * Initializes the pattern.
182
	 * @param \Prado\Xml\TXmlElement $config configuration for this module.
183
	 * @throws TConfigurationException if service parameter is not specified
184
	 */
185 1
	public function init($config)
0 ignored issues
show
Unused Code introduced by
The parameter $config is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
	public function init(/** @scrutinizer ignore-unused */ $config)

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

Loading history...
186
	{
187 1
		if ($this->_serviceParameter === null) {
188
			throw new TConfigurationException('urlmappingpattern_serviceparameter_required', $this->getPattern());
189
		}
190 1
		if (strpos($this->_serviceParameter, '*') !== false) {
191
			$this->_isWildCardPattern = true;
192
		}
193 1
	}
194
195
	/**
196
	 * Substitute the parameter key value pairs as named groupings
197
	 * in the regular expression matching pattern.
198
	 * @return string regular expression pattern with parameter subsitution
199
	 */
200 1
	protected function getParameterizedPattern()
201
	{
202 1
		$params = [];
203 1
		$values = [];
204 1
		if ($this->_parameters) {
205 1
			foreach ($this->_parameters as $key => $value) {
206 1
				$params[] = '{' . $key . '}';
207 1
				$values[] = '(?P<' . $key . '>' . $value . ')';
208
			}
209
		}
210 1
		if ($this->getIsWildCardPattern()) {
211
			$params[] = '{*}';
212
			// service parameter must not contain '=' and '/'
213
			$values[] = '(?P<' . $this->getServiceID() . '>[^=/]+)';
214
		}
215 1
		$params[] = '/';
216 1
		$values[] = '\\/';
217 1
		$regexp = str_replace($params, $values, trim($this->getPattern(), '/') . '/');
218 1
		if ($this->_urlFormat === THttpRequestUrlFormat::Get) {
219 1
			$regexp = '/^' . $regexp . '$/u';
220
		} else {
221
			$regexp = '/^' . $regexp . '(?P<urlparams>.*)$/u';
222
		}
223
224 1
		if (!$this->getCaseSensitive()) {
225
			$regexp .= 'i';
226
		}
227 1
		return $regexp;
228
	}
229
230
	/**
231
	 * @return string full regular expression mapping pattern
232
	 */
233 1
	public function getRegularExpression()
234
	{
235 1
		return $this->_regexp;
236
	}
237
238
	/**
239
	 * @param string $value full regular expression mapping pattern.
240
	 */
241
	public function setRegularExpression($value)
242
	{
243
		$this->_regexp = $value;
244
	}
245
246
	/**
247
	 * @return bool whether the {@see getPattern Pattern} should be treated as case sensititve. Defaults to true.
248
	 */
249 1
	public function getCaseSensitive()
250
	{
251 1
		return $this->_caseSensitive;
252
	}
253
254
	/**
255
	 * @param bool $value whether the {@see getPattern Pattern} should be treated as case sensititve.
256
	 */
257
	public function setCaseSensitive($value)
258
	{
259
		$this->_caseSensitive = TPropertyValue::ensureBoolean($value);
260
	}
261
262
	/**
263
	 * @param string $value service parameter, such as page class name.
264
	 */
265 1
	public function setServiceParameter($value)
266
	{
267 1
		$this->_serviceParameter = $value;
268 1
	}
269
270
	/**
271
	 * @return string service parameter, such as page class name.
272
	 */
273 1
	public function getServiceParameter()
274
	{
275 1
		return $this->_serviceParameter;
276
	}
277
278
	/**
279
	 * @param string $value service id to handle.
280
	 */
281 1
	public function setServiceID($value)
282
	{
283 1
		$this->_serviceID = $value;
284 1
	}
285
286
	/**
287
	 * @return string service id.
288
	 */
289 1
	public function getServiceID()
290
	{
291 1
		return $this->_serviceID;
292
	}
293
294
	/**
295
	 * @return string url pattern to match. Defaults to ''.
296
	 */
297 1
	public function getPattern()
298
	{
299 1
		return $this->_pattern;
300
	}
301
302
	/**
303
	 * @param string $value url pattern to match.
304
	 */
305 1
	public function setPattern($value)
306
	{
307 1
		$this->_pattern = $value;
308 1
	}
309
310
	/**
311
	 * @return TAttributeCollection parameter key value pairs.
312
	 */
313 1
	public function getParameters()
314
	{
315 1
		if (!$this->_parameters) {
316 1
			$this->_parameters = new TAttributeCollection();
317 1
			$this->_parameters->setCaseSensitive(true);
318
		}
319 1
		return $this->_parameters;
320
	}
321
322
	/**
323
	 * @param TAttributeCollection $value new parameter key value pairs.
324
	 */
325
	public function setParameters($value)
326
	{
327
		$this->_parameters = $value;
328
	}
329
330
	/**
331
	 * @return TAttributeCollection constanst parameter key value pairs.
332
	 * @since 3.2.2
333
	 */
334
	public function getConstants()
335
	{
336
		if (!$this->_constants) {
337
			$this->_constants = new TAttributeCollection();
338
			$this->_constants->setCaseSensitive(true);
339
		}
340
		return $this->_constants;
341
	}
342
343
	/**
344
	 * Uses URL pattern (or full regular expression if available) to
345
	 * match the given url path.
346
	 * @param THttpRequest $request the request module
347
	 * @return array matched parameters, empty if no matches.
348
	 */
349 1
	public function getPatternMatches($request)
350
	{
351 1
		$matches = [];
352 1
		if (($pattern = $this->getRegularExpression()) !== '') {
353
			preg_match($pattern, $request->getPathInfo(), $matches);
354
		} else {
355 1
			preg_match($this->getParameterizedPattern(), trim($request->getPathInfo(), '/') . '/', $matches);
356
		}
357
358 1
		if ($this->getIsWildCardPattern() && isset($matches[$this->_serviceID])) {
359
			$matches[$this->_serviceID] = str_replace('*', $matches[$this->_serviceID], $this->_serviceParameter);
360
		}
361
362 1
		if (isset($matches['urlparams'])) {
363
			$params = explode('/', $matches['urlparams']);
364
			if ($this->_separator === '/') {
365
				while ($key = array_shift($params)) {
366
					$matches[$key] = ($value = array_shift($params)) ? $value : '';
367
				}
368
			} else {
369
				array_pop($params);
370
				foreach ($params as $param) {
371
					[$key, $value] = explode($this->_separator, $param, 2);
372
					$matches[$key] = $value;
373
				}
374
			}
375
			unset($matches['urlparams']);
376
		}
377
378 1
		if (count($matches) > 0 && $this->_constants) {
379
			foreach ($this->_constants->toArray() as $key => $value) {
380
				$matches[$key] = $value;
381
			}
382
		}
383
384 1
		return $matches;
385
	}
386
387
	/**
388
	 * Returns a value indicating whether to use this pattern to construct URL.
389
	 * @return bool whether to enable custom constructUrl. Defaults to true.
390
	 * @since 3.1.1
391
	 */
392
	public function getEnableCustomUrl()
393
	{
394
		return $this->_customUrl;
395
	}
396
397
	/**
398
	 * Sets a value indicating whether to enable custom constructUrl using this pattern
399
	 * @param bool $value whether to enable custom constructUrl.
400
	 */
401
	public function setEnableCustomUrl($value)
402
	{
403
		$this->_customUrl = TPropertyValue::ensureBoolean($value);
404
	}
405
406
	/**
407
	 * @return bool whether this pattern is a wildcard pattern
408
	 * @since 3.1.4
409
	 */
410 1
	public function getIsWildCardPattern()
411
	{
412 1
		return $this->_isWildCardPattern;
413
	}
414
415
	/**
416
	 * @return THttpRequestUrlFormat the format of URLs. Defaults to THttpRequestUrlFormat::Get.
417
	 */
418
	public function getUrlFormat()
419
	{
420
		return $this->_urlFormat;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_urlFormat returns the type string which is incompatible with the documented return type Prado\Web\THttpRequestUrlFormat.
Loading history...
421
	}
422
423
	/**
424
	 * Sets the format of URLs constructed and interpreted by this pattern.
425
	 * A Get URL format is like index.php?name1=value1&name2=value2
426
	 * while a Path URL format is like index.php/name1/value1/name2/value.
427
	 * The separating character between name and value can be configured with
428
	 * {@see setUrlParamSeparator} and defaults to '/'.
429
	 * Changing the UrlFormat will affect {@see constructUrl} and how GET variables
430
	 * are parsed.
431
	 * @param THttpRequestUrlFormat $value the format of URLs.
432
	 * @since 3.1.4
433
	 */
434
	public function setUrlFormat($value)
435
	{
436
		$this->_urlFormat = TPropertyValue::ensureEnum($value, THttpRequestUrlFormat::class);
437
	}
438
439
	/**
440
	 * @return string separator used to separate GET variable name and value when URL format is Path. Defaults to slash '/'.
441
	 */
442
	public function getUrlParamSeparator()
443
	{
444
		return $this->_separator;
445
	}
446
447
	/**
448
	 * @param string $value separator used to separate GET variable name and value when URL format is Path.
449
	 * @throws TInvalidDataValueException if the separator is not a single character
450
	 */
451
	public function setUrlParamSeparator($value)
452
	{
453
		if (strlen($value) === 1) {
454
			$this->_separator = $value;
455
		} else {
456
			throw new TInvalidDataValueException('httprequest_separator_invalid');
457
		}
458
	}
459
460
	/**
461
	 * @return TUrlMappingPatternSecureConnection the SecureConnection behavior. Defaults to {@see \Prado\Web\TUrlMappingPatternSecureConnection::Automatic Automatic}
462
	 * @since 3.2
463
	 */
464
	public function getSecureConnection()
465
	{
466
		return $this->_secureConnection;
467
	}
468
469
	/**
470
	 * @param TUrlMappingPatternSecureConnection $value the SecureConnection behavior.
471
	 * @since 3.2
472
	 */
473
	public function setSecureConnection($value)
474
	{
475
		$this->_secureConnection = TPropertyValue::ensureEnum($value, TUrlMappingPatternSecureConnection::class);
0 ignored issues
show
Documentation Bug introduced by
It seems like Prado\TPropertyValue::en...ecureConnection::class) of type string is incompatible with the declared type Prado\Web\TUrlMappingPatternSecureConnection of property $_secureConnection.

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

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

Loading history...
476
	}
477
478
	/**
479
	 * @param array $getItems list of GET items to be put in the constructed URL
480
	 * @return bool whether this pattern IS the one for constructing the URL with the specified GET items.
481
	 * @since 3.1.1
482
	 */
483
	public function supportCustomUrl($getItems)
484
	{
485
		if (!$this->_customUrl || $this->getPattern() === null) {
486
			return false;
487
		}
488
		if ($this->_parameters) {
489
			foreach ($this->_parameters as $key => $value) {
490
				if (!isset($getItems[$key])) {
491
					return false;
492
				}
493
			}
494
		}
495
496
		if ($this->_constants) {
497
			foreach ($this->_constants->toArray() as $key => $value) {
498
				if (!isset($getItems[$key])) {
499
					return false;
500
				}
501
				if ($getItems[$key] != $value) {
502
					return false;
503
				}
504
			}
505
		}
506
		return true;
507
	}
508
509
	/**
510
	 * Constructs a URL using this pattern.
511
	 * @param array $getItems list of GET variables
512
	 * @param bool $encodeAmpersand whether the ampersand should be encoded in the constructed URL
513
	 * @param bool $encodeGetItems whether the GET variables should be encoded in the constructed URL
514
	 * @return string the constructed URL
515
	 * @since 3.1.1
516
	 */
517
	public function constructUrl($getItems, $encodeAmpersand, $encodeGetItems)
518
	{
519
		if ($this->_constants) {
520
			foreach ($this->_constants->toArray() as $key => $value) {
521
				unset($getItems[$key]);
522
			}
523
		}
524
525
		$extra = [];
526
		$replace = [];
527
		// for the GET variables matching the pattern, put them in the URL path
528
		foreach ($getItems as $key => $value) {
529
			if (($this->_parameters && $this->_parameters->contains($key)) || ($key === '*' && $this->getIsWildCardPattern())) {
530
				$replace['{' . $key . '}'] = $encodeGetItems ? rawurlencode($value) : $value;
531
			} else {
532
				$extra[$key] = $value;
533
			}
534
		}
535
536
		$url = $this->_manager->getUrlPrefix() . '/' . ltrim(strtr($this->getPattern(), $replace), '/');
0 ignored issues
show
Bug introduced by
The method getUrlPrefix() does not exist on Prado\Web\TUrlManager. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

536
		$url = $this->_manager->/** @scrutinizer ignore-call */ getUrlPrefix() . '/' . ltrim(strtr($this->getPattern(), $replace), '/');
Loading history...
537
538
		// for the rest of the GET variables, put them in the query string
539
		if (count($extra) > 0) {
540
			if ($this->_urlFormat === THttpRequestUrlFormat::Path && $this->getIsWildCardPattern()) {
541
				foreach ($extra as $name => $value) {
542
					$url .= '/' . $name . $this->_separator . ($encodeGetItems ? rawurlencode($value) : $value);
543
				}
544
				return $url;
545
			}
546
547
			$url2 = '';
548
			$amp = $encodeAmpersand ? '&amp;' : '&';
549
			if ($encodeGetItems) {
550
				foreach ($extra as $name => $value) {
551
					if (is_array($value)) {
552
						$name = rawurlencode($name . '[]');
553
						foreach ($value as $v) {
554
							$url2 .= $amp . $name . '=' . rawurlencode($v);
555
						}
556
					} else {
557
						$url2 .= $amp . rawurlencode($name) . '=' . rawurlencode($value);
558
					}
559
				}
560
			} else {
561
				foreach ($extra as $name => $value) {
562
					if (is_array($value)) {
563
						foreach ($value as $v) {
564
							$url2 .= $amp . $name . '[]=' . $v;
565
						}
566
					} else {
567
						$url2 .= $amp . $name . '=' . $value;
568
					}
569
				}
570
			}
571
			$url = $url . '?' . substr($url2, strlen($amp));
572
		}
573
		return $this -> applySecureConnectionPrefix($url);
574
	}
575
576
	/**
577
	 * Apply behavior of {@see SecureConnection} property by conditionaly prefixing
578
	 * URL with {@see \Prado\Web\THttpRequest::getBaseUrl()}
579
	 *
580
	 * @param string $url
581
	 * @return string
582
	 * @since 3.2
583
	 */
584
	protected function applySecureConnectionPrefix($url)
585
	{
586
		static $request;
587
		if ($request === null) {
588
			$request = Prado::getApplication() -> getRequest();
589
		}
590
591
		static $isSecureConnection;
592
		if ($isSecureConnection === null) {
593
			$isSecureConnection = $request -> getIsSecureConnection();
594
		}
595
596
		switch ($this -> getSecureConnection()) {
597
			case TUrlMappingPatternSecureConnection::EnableIfNotSecure:
598
				if ($isSecureConnection) {
599
					return $url;
600
				}
601
				return $request -> getBaseUrl(true) . $url;
602
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
603
			case TUrlMappingPatternSecureConnection::DisableIfSecure:
604
				if (!$isSecureConnection) {
605
					return $url;
606
				}
607
				return $request -> getBaseUrl(false) . $url;
608
				break;
609
			case TUrlMappingPatternSecureConnection::Enable:
610
				return $request -> getBaseUrl(true) . $url;
611
				break;
612
			case TUrlMappingPatternSecureConnection::Disable:
613
				return $request -> getBaseUrl(false) . $url;
614
				break;
615
			case TUrlMappingPatternSecureConnection::Automatic:
616
			default:
617
				return $url;
618
				break;
619
		}
620
	}
621
}
622