1<?php
2/**
3 * CUrlManager class file
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.yiiframework.com/
7 * @copyright 2008-2013 Yii Software LLC
8 * @license http://www.yiiframework.com/license/
9 */
10
11/**
12 * CUrlManager manages the URLs of Yii Web applications.
13 *
14 * It provides URL construction ({@link createUrl()}) as well as parsing ({@link parseUrl()}) functionality.
15 *
16 * URLs managed via CUrlManager can be in one of the following two formats,
17 * by setting {@link setUrlFormat urlFormat} property:
18 * <ul>
19 * <li>'path' format: /path/to/EntryScript.php/name1/value1/name2/value2...</li>
20 * <li>'get' format:  /path/to/EntryScript.php?name1=value1&name2=value2...</li>
21 * </ul>
22 *
23 * When using 'path' format, CUrlManager uses a set of {@link setRules rules} to:
24 * <ul>
25 * <li>parse the requested URL into a route ('ControllerID/ActionID') and GET parameters;</li>
26 * <li>create URLs based on the given route and GET parameters.</li>
27 * </ul>
28 *
29 * A rule consists of a route and a pattern. The latter is used by CUrlManager to determine
30 * which rule is used for parsing/creating URLs. A pattern is meant to match the path info
31 * part of a URL. It may contain named parameters using the syntax '&lt;ParamName:RegExp&gt;'.
32 *
33 * When parsing a URL, a matching rule will extract the named parameters from the path info
34 * and put them into the $_GET variable; when creating a URL, a matching rule will extract
35 * the named parameters from $_GET and put them into the path info part of the created URL.
36 *
37 * If a pattern ends with '/*', it means additional GET parameters may be appended to the path
38 * info part of the URL; otherwise, the GET parameters can only appear in the query string part.
39 *
40 * To specify URL rules, set the {@link setRules rules} property as an array of rules (pattern=>route).
41 * For example,
42 * <pre>
43 * array(
44 *     'articles'=>'article/list',
45 *     'article/<id:\d+>/*'=>'article/read',
46 * )
47 * </pre>
48 * Two rules are specified in the above:
49 * <ul>
50 * <li>The first rule says that if the user requests the URL '/path/to/index.php/articles',
51 *   it should be treated as '/path/to/index.php/article/list'; and vice versa applies
52 *   when constructing such a URL.</li>
53 * <li>The second rule contains a named parameter 'id' which is specified using
54 *   the &lt;ParamName:RegExp&gt; syntax. It says that if the user requests the URL
55 *   '/path/to/index.php/article/13', it should be treated as '/path/to/index.php/article/read?id=13';
56 *   and vice versa applies when constructing such a URL.</li>
57 * </ul>
58 *
59 * The route part may contain references to named parameters defined in the pattern part.
60 * This allows a rule to be applied to different routes based on matching criteria.
61 * For example,
62 * <pre>
63 * array(
64 *      '<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>'=>'<_c>/<_a>',
65 *      '<_c:(post|comment)>/<id:\d+>'=>'<_c>/view',
66 *      '<_c:(post|comment)>s/*'=>'<_c>/list',
67 * )
68 * </pre>
69 * In the above, we use two named parameters '<_c>' and '<_a>' in the route part. The '<_c>'
70 * parameter matches either 'post' or 'comment', while the '<_a>' parameter matches an action ID.
71 *
72 * Like normal rules, these rules can be used for both parsing and creating URLs.
73 * For example, using the rules above, the URL '/index.php/post/123/create'
74 * would be parsed as the route 'post/create' with GET parameter 'id' being 123.
75 * And given the route 'post/list' and GET parameter 'page' being 2, we should get a URL
76 * '/index.php/posts/page/2'.
77 *
78 * It is also possible to include hostname into the rules for parsing and creating URLs.
79 * One may extract part of the hostname to be a GET parameter.
80 * For example, the URL <code>http://admin.example.com/en/profile</code> may be parsed into GET parameters
81 * <code>user=admin</code> and <code>lang=en</code>. On the other hand, rules with hostname may also be used to
82 * create URLs with parameterized hostnames.
83 *
84 * In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:
85 * <pre>
86 * array(
87 *     'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
88 * )
89 * </pre>
90 *
91 * Starting from version 1.1.8, one can write custom URL rule classes and use them for one or several URL rules.
92 * For example,
93 * <pre>
94 * array(
95 *   // a standard rule
96 *   '<action:(login|logout)>' => 'site/<action>',
97 *   // a custom rule using data in DB
98 *   array(
99 *     'class' => 'application.components.MyUrlRule',
100 *     'connectionID' => 'db',
101 *   ),
102 * )
103 * </pre>
104 * Please note that the custom URL rule class should extend from {@link CBaseUrlRule} and
105 * implement the following two methods,
106 * <ul>
107 *    <li>{@link CBaseUrlRule::createUrl()}</li>
108 *    <li>{@link CBaseUrlRule::parseUrl()}</li>
109 * </ul>
110 *
111 * CUrlManager is a default application component that may be accessed via
112 * {@link CWebApplication::getUrlManager()}.
113 *
114 * @property string $baseUrl The base URL of the application (the part after host name and before query string).
115 * If {@link showScriptName} is true, it will include the script name part.
116 * Otherwise, it will not, and the ending slashes are stripped off.
117 * @property string $urlFormat The URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
118 * Please refer to the guide for more details about the difference between these two formats.
119 *
120 * @author Qiang Xue <qiang.xue@gmail.com>
121 * @package system.web
122 * @since 1.0
123 */
124class CUrlManager extends CApplicationComponent
125{
126	const CACHE_KEY='Yii.CUrlManager.rules';
127	const GET_FORMAT='get';
128	const PATH_FORMAT='path';
129
130	/**
131	 * @var array the URL rules (pattern=>route).
132	 */
133	public $rules=array();
134	/**
135	 * @var string the URL suffix used when in 'path' format.
136	 * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. Defaults to empty.
137	 */
138	public $urlSuffix='';
139	/**
140	 * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
141	 */
142	public $showScriptName=true;
143	/**
144	 * @var boolean whether to append GET parameters to the path info part. Defaults to true.
145	 * This property is only effective when {@link urlFormat} is 'path' and is mainly used when
146	 * creating URLs. When it is true, GET parameters will be appended to the path info and
147	 * separate from each other using slashes. If this is false, GET parameters will be in query part.
148	 */
149	public $appendParams=true;
150	/**
151	 * @var string the GET variable name for route. Defaults to 'r'.
152	 */
153	public $routeVar='r';
154	/**
155	 * @var boolean whether routes are case-sensitive. Defaults to true. By setting this to false,
156	 * the route in the incoming request will be turned to lower case first before further processing.
157	 * As a result, you should follow the convention that you use lower case when specifying
158	 * controller mapping ({@link CWebApplication::controllerMap}) and action mapping
159	 * ({@link CController::actions}). Also, the directory names for organizing controllers should
160	 * be in lower case.
161	 */
162	public $caseSensitive=true;
163	/**
164	 * @var boolean whether the GET parameter values should match the corresponding
165	 * sub-patterns in a rule before using it to create a URL. Defaults to false, meaning
166	 * a rule will be used for creating a URL only if its route and parameter names match the given ones.
167	 * If this property is set true, then the given parameter values must also match the corresponding
168	 * parameter sub-patterns. Note that setting this property to true will degrade performance.
169	 * @since 1.1.0
170	 */
171	public $matchValue=false;
172	/**
173	 * @var string the ID of the cache application component that is used to cache the parsed URL rules.
174	 * Defaults to 'cache' which refers to the primary cache application component.
175	 * Set this property to false if you want to disable caching URL rules.
176	 */
177	public $cacheID='cache';
178	/**
179	 * @var boolean whether to enable strict URL parsing.
180	 * This property is only effective when {@link urlFormat} is 'path'.
181	 * If it is set true, then an incoming URL must match one of the {@link rules URL rules}.
182	 * Otherwise, it will be treated as an invalid request and trigger a 404 HTTP exception.
183	 * Defaults to false.
184	 */
185	public $useStrictParsing=false;
186	/**
187	 * @var string the class name or path alias for the URL rule instances. Defaults to 'CUrlRule'.
188	 * If you change this to something else, please make sure that the new class must extend from
189	 * {@link CBaseUrlRule} and have the same constructor signature as {@link CUrlRule}.
190	 * It must also be serializable and autoloadable.
191	 * @since 1.1.8
192	 */
193	public $urlRuleClass='CUrlRule';
194
195	private $_urlFormat=self::GET_FORMAT;
196	private $_rules=array();
197	private $_baseUrl;
198
199
200	/**
201	 * Initializes the application component.
202	 */
203	public function init()
204	{
205		parent::init();
206		$this->processRules();
207	}
208
209	/**
210	 * Processes the URL rules.
211	 */
212	protected function processRules()
213	{
214		if(empty($this->rules) || $this->getUrlFormat()===self::GET_FORMAT)
215			return;
216		if($this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null)
217		{
218			$hash=md5(serialize($this->rules));
219			if(($data=$cache->get(self::CACHE_KEY))!==false && isset($data[1]) && $data[1]===$hash)
220			{
221				$this->_rules=$data[0];
222				return;
223			}
224		}
225		foreach($this->rules as $pattern=>$route)
226			$this->_rules[]=$this->createUrlRule($route,$pattern);
227		if(isset($cache))
228			$cache->set(self::CACHE_KEY,array($this->_rules,$hash));
229	}
230
231	/**
232	 * Adds new URL rules.
233	 * In order to make the new rules effective, this method must be called BEFORE
234	 * {@link CWebApplication::processRequest}.
235	 * @param array $rules new URL rules (pattern=>route).
236	 * @param boolean $append whether the new URL rules should be appended to the existing ones. If false,
237	 * they will be inserted at the beginning.
238	 * @since 1.1.4
239	 */
240	public function addRules($rules,$append=true)
241	{
242		if ($append)
243		{
244			foreach($rules as $pattern=>$route)
245				$this->_rules[]=$this->createUrlRule($route,$pattern);
246		}
247		else
248		{
249			$rules=array_reverse($rules);
250			foreach($rules as $pattern=>$route)
251				array_unshift($this->_rules, $this->createUrlRule($route,$pattern));
252		}
253	}
254
255	/**
256	 * Creates a URL rule instance.
257	 * The default implementation returns a CUrlRule object.
258	 * @param mixed $route the route part of the rule. This could be a string or an array
259	 * @param string $pattern the pattern part of the rule
260	 * @return CUrlRule the URL rule instance
261	 * @since 1.1.0
262	 */
263	protected function createUrlRule($route,$pattern)
264	{
265		if(is_array($route) && isset($route['class']))
266			return $route;
267		else
268		{
269			$urlRuleClass=Yii::import($this->urlRuleClass,true);
270			return new $urlRuleClass($route,$pattern);
271		}
272	}
273
274	/**
275	 * Constructs a URL.
276	 * @param string $route the controller and the action (e.g. article/read)
277	 * @param array $params list of GET parameters (name=>value). Both the name and value will be URL-encoded.
278	 * If the name is '#', the corresponding value will be treated as an anchor
279	 * and will be appended at the end of the URL.
280	 * @param string $ampersand the token separating name-value pairs in the URL. Defaults to '&'.
281	 * @return string the constructed URL
282	 */
283	public function createUrl($route,$params=array(),$ampersand='&')
284	{
285		unset($params[$this->routeVar]);
286		foreach($params as $i=>$param)
287			if($param===null)
288				$params[$i]='';
289
290		if(isset($params['#']))
291		{
292			$anchor='#'.$params['#'];
293			unset($params['#']);
294		}
295		else
296			$anchor='';
297		$route=trim($route,'/');
298		foreach($this->_rules as $i=>$rule)
299		{
300			if(is_array($rule))
301				$this->_rules[$i]=$rule=Yii::createComponent($rule);
302			if(($url=$rule->createUrl($this,$route,$params,$ampersand))!==false)
303			{
304				if($rule->hasHostInfo)
305					return $url==='' ? '/'.$anchor : $url.$anchor;
306				else
307					return $this->getBaseUrl().'/'.$url.$anchor;
308			}
309		}
310		return $this->createUrlDefault($route,$params,$ampersand).$anchor;
311	}
312
313	/**
314	 * Creates a URL based on default settings.
315	 * @param string $route the controller and the action (e.g. article/read)
316	 * @param array $params list of GET parameters
317	 * @param string $ampersand the token separating name-value pairs in the URL.
318	 * @return string the constructed URL
319	 */
320	protected function createUrlDefault($route,$params,$ampersand)
321	{
322		if($this->getUrlFormat()===self::PATH_FORMAT)
323		{
324			$url=rtrim($this->getBaseUrl().'/'.$route,'/');
325			if($this->appendParams)
326			{
327				$url=rtrim($url.'/'.$this->createPathInfo($params,'/','/'),'/');
328				return $route==='' ? $url : $url.$this->urlSuffix;
329			}
330			else
331			{
332				if($route!=='')
333					$url.=$this->urlSuffix;
334				$query=$this->createPathInfo($params,'=',$ampersand);
335				return $query==='' ? $url : $url.'?'.$query;
336			}
337		}
338		else
339		{
340			$url=$this->getBaseUrl();
341			if(!$this->showScriptName)
342				$url.='/';
343			if($route!=='')
344			{
345				$url.='?'.$this->routeVar.'='.$route;
346				if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
347					$url.=$ampersand.$query;
348			}
349			elseif(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
350				$url.='?'.$query;
351			return $url;
352		}
353	}
354
355	/**
356	 * Parses the user request.
357	 * @param CHttpRequest $request the request application component
358	 * @return string the route (controllerID/actionID) and perhaps GET parameters in path format.
359	 * @throws CException
360	 * @throws CHttpException
361	 */
362	public function parseUrl($request)
363	{
364		if($this->getUrlFormat()===self::PATH_FORMAT)
365		{
366			$rawPathInfo=$request->getPathInfo();
367			$pathInfo=$this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
368			foreach($this->_rules as $i=>$rule)
369			{
370				if(is_array($rule))
371					$this->_rules[$i]=$rule=Yii::createComponent($rule);
372				if(($r=$rule->parseUrl($this,$request,$pathInfo,$rawPathInfo))!==false)
373					return isset($_GET[$this->routeVar]) ? $_GET[$this->routeVar] : $r;
374			}
375			if($this->useStrictParsing)
376				throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
377					array('{route}'=>$pathInfo)));
378			else
379				return $pathInfo;
380		}
381		elseif(isset($_GET[$this->routeVar]))
382			return $_GET[$this->routeVar];
383		elseif(isset($_POST[$this->routeVar]))
384			return $_POST[$this->routeVar];
385		else
386			return '';
387	}
388
389	/**
390	 * Parses a path info into URL segments and saves them to $_GET and $_REQUEST.
391	 * @param string $pathInfo path info
392	 */
393	public function parsePathInfo($pathInfo)
394	{
395		if($pathInfo==='')
396			return;
397		$segs=explode('/',$pathInfo.'/');
398		$n=count($segs);
399		for($i=0;$i<$n-1;$i+=2)
400		{
401			$key=$segs[$i];
402			if($key==='') continue;
403			$value=$segs[$i+1];
404			if(($pos=strpos($key,'['))!==false && ($m=preg_match_all('/\[(.*?)\]/',$key,$matches))>0)
405			{
406				$name=substr($key,0,$pos);
407				for($j=$m-1;$j>=0;--$j)
408				{
409					if($matches[1][$j]==='')
410						$value=array($value);
411					else
412						$value=array($matches[1][$j]=>$value);
413				}
414				if(isset($_GET[$name]) && is_array($_GET[$name]))
415					$value=CMap::mergeArray($_GET[$name],$value);
416				$_REQUEST[$name]=$_GET[$name]=$value;
417			}
418			else
419				$_REQUEST[$key]=$_GET[$key]=$value;
420		}
421	}
422
423	/**
424	 * Creates a path info based on the given parameters.
425	 * @param array $params list of GET parameters
426	 * @param string $equal the separator between name and value
427	 * @param string $ampersand the separator between name-value pairs
428	 * @param string $key this is used internally.
429	 * @return string the created path info
430	 */
431	public function createPathInfo($params,$equal,$ampersand, $key=null)
432	{
433		$pairs = array();
434		foreach($params as $k => $v)
435		{
436			if ($key!==null)
437				$k = $key.'['.$k.']';
438
439			if (is_array($v))
440				$pairs[]=$this->createPathInfo($v,$equal,$ampersand, $k);
441			else
442				$pairs[]=urlencode($k).$equal.urlencode($v);
443		}
444		return implode($ampersand,$pairs);
445	}
446
447	/**
448	 * Removes the URL suffix from path info.
449	 * @param string $pathInfo path info part in the URL
450	 * @param string $urlSuffix the URL suffix to be removed
451	 * @return string path info with URL suffix removed.
452	 */
453	public function removeUrlSuffix($pathInfo,$urlSuffix)
454	{
455		if($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
456			return substr($pathInfo,0,-strlen($urlSuffix));
457		else
458			return $pathInfo;
459	}
460
461	/**
462	 * Returns the base URL of the application.
463	 * @return string the base URL of the application (the part after host name and before query string).
464	 * If {@link showScriptName} is true, it will include the script name part.
465	 * Otherwise, it will not, and the ending slashes are stripped off.
466	 */
467	public function getBaseUrl()
468	{
469		if($this->_baseUrl!==null)
470			return $this->_baseUrl;
471		else
472		{
473			if($this->showScriptName)
474				$this->_baseUrl=Yii::app()->getRequest()->getScriptUrl();
475			else
476				$this->_baseUrl=Yii::app()->getRequest()->getBaseUrl();
477			return $this->_baseUrl;
478		}
479	}
480
481	/**
482	 * Sets the base URL of the application (the part after host name and before query string).
483	 * This method is provided in case the {@link baseUrl} cannot be determined automatically.
484	 * The ending slashes should be stripped off. And you are also responsible to remove the script name
485	 * if you set {@link showScriptName} to be false.
486	 * @param string $value the base URL of the application
487	 * @since 1.1.1
488	 */
489	public function setBaseUrl($value)
490	{
491		$this->_baseUrl=$value;
492	}
493
494	/**
495	 * Returns the URL format.
496	 * @return string the URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
497	 * Please refer to the guide for more details about the difference between these two formats.
498	 */
499	public function getUrlFormat()
500	{
501		return $this->_urlFormat;
502	}
503
504	/**
505	 * Sets the URL format.
506	 * @param string $value the URL format. It must be either 'path' or 'get'.
507	 * @throws CException
508	 */
509	public function setUrlFormat($value)
510	{
511		if($value===self::PATH_FORMAT || $value===self::GET_FORMAT)
512			$this->_urlFormat=$value;
513		else
514			throw new CException(Yii::t('yii','CUrlManager.UrlFormat must be either "path" or "get".'));
515	}
516}
517
518
519/**
520 * CBaseUrlRule is the base class for a URL rule class.
521 *
522 * Custom URL rule classes should extend from this class and implement two methods:
523 * {@link createUrl} and {@link parseUrl}.
524 *
525 * @author Qiang Xue <qiang.xue@gmail.com>
526 * @package system.web
527 * @since 1.1.8
528 */
529abstract class CBaseUrlRule extends CComponent
530{
531	/**
532	 * @var boolean whether this rule will also parse the host info part. Defaults to false.
533	 */
534	public $hasHostInfo=false;
535	/**
536	 * Creates a URL based on this rule.
537	 * @param CUrlManager $manager the manager
538	 * @param string $route the route
539	 * @param array $params list of parameters (name=>value) associated with the route
540	 * @param string $ampersand the token separating name-value pairs in the URL.
541	 * @return mixed the constructed URL. False if this rule does not apply.
542	 */
543	abstract public function createUrl($manager,$route,$params,$ampersand);
544	/**
545	 * Parses a URL based on this rule.
546	 * @param CUrlManager $manager the URL manager
547	 * @param CHttpRequest $request the request object
548	 * @param string $pathInfo path info part of the URL (URL suffix is already removed based on {@link CUrlManager::urlSuffix})
549	 * @param string $rawPathInfo path info that contains the potential URL suffix
550	 * @return mixed the route that consists of the controller ID and action ID. False if this rule does not apply.
551	 */
552	abstract public function parseUrl($manager,$request,$pathInfo,$rawPathInfo);
553}
554
555/**
556 * CUrlRule represents a URL formatting/parsing rule.
557 *
558 * It mainly consists of two parts: route and pattern. The former classifies
559 * the rule so that it only applies to specific controller-action route.
560 * The latter performs the actual formatting and parsing role. The pattern
561 * may have a set of named parameters.
562 *
563 * @author Qiang Xue <qiang.xue@gmail.com>
564 * @package system.web
565 * @since 1.0
566 */
567class CUrlRule extends CBaseUrlRule
568{
569	/**
570	 * @var string the URL suffix used for this rule.
571	 * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
572	 * Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
573	 */
574	public $urlSuffix;
575	/**
576	 * @var boolean whether the rule is case sensitive. Defaults to null, meaning
577	 * using the value of {@link CUrlManager::caseSensitive}.
578	 */
579	public $caseSensitive;
580	/**
581	 * @var array the default GET parameters (name=>value) that this rule provides.
582	 * When this rule is used to parse the incoming request, the values declared in this property
583	 * will be injected into $_GET.
584	 */
585	public $defaultParams=array();
586	/**
587	 * @var boolean whether the GET parameter values should match the corresponding
588	 * sub-patterns in the rule when creating a URL. Defaults to null, meaning using the value
589	 * of {@link CUrlManager::matchValue}. When this property is false, it means
590	 * a rule will be used for creating a URL if its route and parameter names match the given ones.
591	 * If this property is set true, then the given parameter values must also match the corresponding
592	 * parameter sub-patterns. Note that setting this property to true will degrade performance.
593	 * @since 1.1.0
594	 */
595	public $matchValue;
596	/**
597	 * @var string the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
598	 * If this rule can match multiple verbs, please separate them with commas.
599	 * If this property is not set, the rule can match any verb.
600	 * Note that this property is only used when parsing a request. It is ignored for URL creation.
601	 * @since 1.1.7
602	 */
603	public $verb;
604	/**
605	 * @var boolean whether this rule is only used for request parsing.
606	 * Defaults to false, meaning the rule is used for both URL parsing and creation.
607	 * @since 1.1.7
608	 */
609	public $parsingOnly=false;
610	/**
611	 * @var string the controller/action pair
612	 */
613	public $route;
614	/**
615	 * @var array the mapping from route param name to token name (e.g. _r1=><1>)
616	 */
617	public $references=array();
618	/**
619	 * @var string the pattern used to match route
620	 */
621	public $routePattern;
622	/**
623	 * @var string regular expression used to parse a URL
624	 */
625	public $pattern;
626	/**
627	 * @var string template used to construct a URL
628	 */
629	public $template;
630	/**
631	 * @var array list of parameters (name=>regular expression)
632	 */
633	public $params=array();
634	/**
635	 * @var boolean whether the URL allows additional parameters at the end of the path info.
636	 */
637	public $append;
638	/**
639	 * @var boolean whether host info should be considered for this rule
640	 */
641	public $hasHostInfo;
642
643	/**
644	 * Callback for preg_replace_callback in counstructor
645	 */
646	protected function escapeRegexpSpecialChars($matches)
647	{
648		return preg_quote($matches[0]);
649	}
650
651	/**
652	 * Constructor.
653	 * @param string $route the route of the URL (controller/action)
654	 * @param string $pattern the pattern for matching the URL
655	 * @throws CException
656	 */
657	public function __construct($route,$pattern)
658	{
659		if(is_array($route))
660		{
661			foreach(array('urlSuffix', 'caseSensitive', 'defaultParams', 'matchValue', 'verb', 'parsingOnly') as $name)
662			{
663				if(isset($route[$name]))
664					$this->$name=$route[$name];
665			}
666			if(isset($route['pattern']))
667				$pattern=$route['pattern'];
668			$route=$route[0];
669		}
670		$this->route=trim($route,'/');
671
672		$tr2['/']=$tr['/']='\\/';
673
674		if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
675		{
676			foreach($matches2[1] as $name)
677				$this->references[$name]="<$name>";
678		}
679
680		$this->hasHostInfo=!strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);
681
682		if($this->verb!==null)
683			$this->verb=preg_split('/[\s,]+/',strtoupper($this->verb),-1,PREG_SPLIT_NO_EMPTY);
684
685		if(preg_match_all('/<(\w+):?(.*?)?>/',$pattern,$matches))
686		{
687			$tokens=array_combine($matches[1],$matches[2]);
688			foreach($tokens as $name=>$value)
689			{
690				if($value==='')
691					$value='[^\/]+';
692				$tr["<$name>"]="(?P<$name>$value)";
693				if(isset($this->references[$name]))
694					$tr2["<$name>"]=$tr["<$name>"];
695				else
696					$this->params[$name]=$value;
697			}
698		}
699		$p=rtrim($pattern,'*');
700		$this->append=$p!==$pattern;
701		$p=trim($p,'/');
702		$this->template=preg_replace('/<(\w+):?.*?>/','<$1>',$p);
703		$p=$this->template;
704		if(!$this->parsingOnly)
705			$p=preg_replace_callback('/(?<=^|>)[^<]+(?=<|$)/',array($this,'escapeRegexpSpecialChars'),$p);
706		$this->pattern='/^'.strtr($p,$tr).'\/';
707		if($this->append)
708			$this->pattern.='/u';
709		else
710			$this->pattern.='$/u';
711
712		if($this->references!==array())
713			$this->routePattern='/^'.strtr($this->route,$tr2).'$/u';
714
715		if(YII_DEBUG && @preg_match($this->pattern,'test')===false)
716			throw new CException(Yii::t('yii','The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.',
717				array('{route}'=>$route,'{pattern}'=>$pattern)));
718	}
719
720	/**
721	 * Creates a URL based on this rule.
722	 * @param CUrlManager $manager the manager
723	 * @param string $route the route
724	 * @param array $params list of parameters
725	 * @param string $ampersand the token separating name-value pairs in the URL.
726	 * @return mixed the constructed URL or false on error
727	 */
728	public function createUrl($manager,$route,$params,$ampersand)
729	{
730		if($this->parsingOnly)
731			return false;
732
733		if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
734			$case='';
735		else
736			$case='i';
737
738		$tr=array();
739		if($route!==$this->route)
740		{
741			if($this->routePattern!==null && preg_match($this->routePattern.$case,$route,$matches))
742			{
743				foreach($this->references as $key=>$name)
744					$tr[$name]=$matches[$key];
745			}
746			else
747				return false;
748		}
749
750		foreach($this->defaultParams as $key=>$value)
751		{
752			if(isset($params[$key]))
753			{
754				if($params[$key]==$value)
755					unset($params[$key]);
756				else
757					return false;
758			}
759		}
760
761		foreach($this->params as $key=>$value)
762			if(!isset($params[$key]))
763				return false;
764
765		if($manager->matchValue && $this->matchValue===null || $this->matchValue)
766		{
767			foreach($this->params as $key=>$value)
768			{
769				if(!preg_match('/\A'.$value.'\z/u'.$case,$params[$key]))
770					return false;
771			}
772		}
773
774		foreach($this->params as $key=>$value)
775		{
776			$tr["<$key>"]=urlencode($params[$key]);
777			unset($params[$key]);
778		}
779
780		$suffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
781
782		$url=strtr($this->template,$tr);
783
784		if($this->hasHostInfo)
785		{
786			$hostInfo=Yii::app()->getRequest()->getHostInfo();
787			if(stripos($url,$hostInfo)===0)
788				$url=substr($url,strlen($hostInfo));
789		}
790
791		if(empty($params))
792			return $url!=='' ? $url.$suffix : $url;
793
794		if($this->append)
795			$url.='/'.$manager->createPathInfo($params,'/','/').$suffix;
796		else
797		{
798			if($url!=='')
799				$url.=$suffix;
800			$url.='?'.$manager->createPathInfo($params,'=',$ampersand);
801		}
802
803		return $url;
804	}
805
806	/**
807	 * Parses a URL based on this rule.
808	 * @param CUrlManager $manager the URL manager
809	 * @param CHttpRequest $request the request object
810	 * @param string $pathInfo path info part of the URL
811	 * @param string $rawPathInfo path info that contains the potential URL suffix
812	 * @return mixed the route that consists of the controller ID and action ID or false on error
813	 */
814	public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
815	{
816		if($this->verb!==null && !in_array($request->getRequestType(), $this->verb, true))
817			return false;
818
819		if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
820			$case='';
821		else
822			$case='i';
823
824		if($this->urlSuffix!==null)
825			$pathInfo=$manager->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
826
827		// URL suffix required, but not found in the requested URL
828		if($manager->useStrictParsing && $pathInfo===$rawPathInfo)
829		{
830			$urlSuffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
831			if($urlSuffix!='' && $urlSuffix!=='/')
832				return false;
833		}
834
835		if($this->hasHostInfo)
836			$pathInfo=strtolower($request->getHostInfo()).rtrim('/'.$pathInfo,'/');
837
838		$pathInfo.='/';
839
840		if(preg_match($this->pattern.$case,$pathInfo,$matches))
841		{
842			foreach($this->defaultParams as $name=>$value)
843			{
844				if(!isset($_GET[$name]))
845					$_REQUEST[$name]=$_GET[$name]=$value;
846			}
847			$tr=array();
848			foreach($matches as $key=>$value)
849			{
850				if(isset($this->references[$key]))
851					$tr[$this->references[$key]]=$value;
852				elseif(isset($this->params[$key]))
853					$_REQUEST[$key]=$_GET[$key]=$value;
854			}
855			if($pathInfo!==$matches[0]) // there're additional GET params
856				$manager->parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
857			if($this->routePattern!==null)
858				return strtr($this->route,$tr);
859			else
860				return $this->route;
861		}
862		else
863			return false;
864	}
865}
866