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 '<ParamName:RegExp>'.
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 <ParamName:RegExp> 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: */
124: class 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: */
360: public function parseUrl($request)
361: {
362: if($this->getUrlFormat()===self::PATH_FORMAT)
363: {
364: $rawPathInfo=$request->getPathInfo();
365: $pathInfo=$this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
366: foreach($this->_rules as $i=>$rule)
367: {
368: if(is_array($rule))
369: $this->_rules[$i]=$rule=Yii::createComponent($rule);
370: if(($r=$rule->parseUrl($this,$request,$pathInfo,$rawPathInfo))!==false)
371: return isset($_GET[$this->routeVar]) ? $_GET[$this->routeVar] : $r;
372: }
373: if($this->useStrictParsing)
374: throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
375: array('{route}'=>$pathInfo)));
376: else
377: return $pathInfo;
378: }
379: elseif(isset($_GET[$this->routeVar]))
380: return $_GET[$this->routeVar];
381: elseif(isset($_POST[$this->routeVar]))
382: return $_POST[$this->routeVar];
383: else
384: return '';
385: }
386:
387: /**
388: * Parses a path info into URL segments and saves them to $_GET and $_REQUEST.
389: * @param string $pathInfo path info
390: */
391: public function parsePathInfo($pathInfo)
392: {
393: if($pathInfo==='')
394: return;
395: $segs=explode('/',$pathInfo.'/');
396: $n=count($segs);
397: for($i=0;$i<$n-1;$i+=2)
398: {
399: $key=$segs[$i];
400: if($key==='') continue;
401: $value=$segs[$i+1];
402: if(($pos=strpos($key,'['))!==false && ($m=preg_match_all('/\[(.*?)\]/',$key,$matches))>0)
403: {
404: $name=substr($key,0,$pos);
405: for($j=$m-1;$j>=0;--$j)
406: {
407: if($matches[1][$j]==='')
408: $value=array($value);
409: else
410: $value=array($matches[1][$j]=>$value);
411: }
412: if(isset($_GET[$name]) && is_array($_GET[$name]))
413: $value=CMap::mergeArray($_GET[$name],$value);
414: $_REQUEST[$name]=$_GET[$name]=$value;
415: }
416: else
417: $_REQUEST[$key]=$_GET[$key]=$value;
418: }
419: }
420:
421: /**
422: * Creates a path info based on the given parameters.
423: * @param array $params list of GET parameters
424: * @param string $equal the separator between name and value
425: * @param string $ampersand the separator between name-value pairs
426: * @param string $key this is used internally.
427: * @return string the created path info
428: */
429: public function createPathInfo($params,$equal,$ampersand, $key=null)
430: {
431: $pairs = array();
432: foreach($params as $k => $v)
433: {
434: if ($key!==null)
435: $k = $key.'['.$k.']';
436:
437: if (is_array($v))
438: $pairs[]=$this->createPathInfo($v,$equal,$ampersand, $k);
439: else
440: $pairs[]=urlencode($k).$equal.urlencode($v);
441: }
442: return implode($ampersand,$pairs);
443: }
444:
445: /**
446: * Removes the URL suffix from path info.
447: * @param string $pathInfo path info part in the URL
448: * @param string $urlSuffix the URL suffix to be removed
449: * @return string path info with URL suffix removed.
450: */
451: public function removeUrlSuffix($pathInfo,$urlSuffix)
452: {
453: if($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
454: return substr($pathInfo,0,-strlen($urlSuffix));
455: else
456: return $pathInfo;
457: }
458:
459: /**
460: * Returns the base URL of the application.
461: * @return string the base URL of the application (the part after host name and before query string).
462: * If {@link showScriptName} is true, it will include the script name part.
463: * Otherwise, it will not, and the ending slashes are stripped off.
464: */
465: public function getBaseUrl()
466: {
467: if($this->_baseUrl!==null)
468: return $this->_baseUrl;
469: else
470: {
471: if($this->showScriptName)
472: $this->_baseUrl=Yii::app()->getRequest()->getScriptUrl();
473: else
474: $this->_baseUrl=Yii::app()->getRequest()->getBaseUrl();
475: return $this->_baseUrl;
476: }
477: }
478:
479: /**
480: * Sets the base URL of the application (the part after host name and before query string).
481: * This method is provided in case the {@link baseUrl} cannot be determined automatically.
482: * The ending slashes should be stripped off. And you are also responsible to remove the script name
483: * if you set {@link showScriptName} to be false.
484: * @param string $value the base URL of the application
485: * @since 1.1.1
486: */
487: public function setBaseUrl($value)
488: {
489: $this->_baseUrl=$value;
490: }
491:
492: /**
493: * Returns the URL format.
494: * @return string the URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
495: * Please refer to the guide for more details about the difference between these two formats.
496: */
497: public function getUrlFormat()
498: {
499: return $this->_urlFormat;
500: }
501:
502: /**
503: * Sets the URL format.
504: * @param string $value the URL format. It must be either 'path' or 'get'.
505: */
506: public function setUrlFormat($value)
507: {
508: if($value===self::PATH_FORMAT || $value===self::GET_FORMAT)
509: $this->_urlFormat=$value;
510: else
511: throw new CException(Yii::t('yii','CUrlManager.UrlFormat must be either "path" or "get".'));
512: }
513: }
514:
515:
516: /**
517: * CBaseUrlRule is the base class for a URL rule class.
518: *
519: * Custom URL rule classes should extend from this class and implement two methods:
520: * {@link createUrl} and {@link parseUrl}.
521: *
522: * @author Qiang Xue <qiang.xue@gmail.com>
523: * @package system.web
524: * @since 1.1.8
525: */
526: abstract class CBaseUrlRule extends CComponent
527: {
528: /**
529: * @var boolean whether this rule will also parse the host info part. Defaults to false.
530: */
531: public $hasHostInfo=false;
532: /**
533: * Creates a URL based on this rule.
534: * @param CUrlManager $manager the manager
535: * @param string $route the route
536: * @param array $params list of parameters (name=>value) associated with the route
537: * @param string $ampersand the token separating name-value pairs in the URL.
538: * @return mixed the constructed URL. False if this rule does not apply.
539: */
540: abstract public function createUrl($manager,$route,$params,$ampersand);
541: /**
542: * Parses a URL based on this rule.
543: * @param CUrlManager $manager the URL manager
544: * @param CHttpRequest $request the request object
545: * @param string $pathInfo path info part of the URL (URL suffix is already removed based on {@link CUrlManager::urlSuffix})
546: * @param string $rawPathInfo path info that contains the potential URL suffix
547: * @return mixed the route that consists of the controller ID and action ID. False if this rule does not apply.
548: */
549: abstract public function parseUrl($manager,$request,$pathInfo,$rawPathInfo);
550: }
551:
552: /**
553: * CUrlRule represents a URL formatting/parsing rule.
554: *
555: * It mainly consists of two parts: route and pattern. The former classifies
556: * the rule so that it only applies to specific controller-action route.
557: * The latter performs the actual formatting and parsing role. The pattern
558: * may have a set of named parameters.
559: *
560: * @author Qiang Xue <qiang.xue@gmail.com>
561: * @package system.web
562: * @since 1.0
563: */
564: class CUrlRule extends CBaseUrlRule
565: {
566: /**
567: * @var string the URL suffix used for this rule.
568: * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
569: * Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
570: */
571: public $urlSuffix;
572: /**
573: * @var boolean whether the rule is case sensitive. Defaults to null, meaning
574: * using the value of {@link CUrlManager::caseSensitive}.
575: */
576: public $caseSensitive;
577: /**
578: * @var array the default GET parameters (name=>value) that this rule provides.
579: * When this rule is used to parse the incoming request, the values declared in this property
580: * will be injected into $_GET.
581: */
582: public $defaultParams=array();
583: /**
584: * @var boolean whether the GET parameter values should match the corresponding
585: * sub-patterns in the rule when creating a URL. Defaults to null, meaning using the value
586: * of {@link CUrlManager::matchValue}. When this property is false, it means
587: * a rule will be used for creating a URL if its route and parameter names match the given ones.
588: * If this property is set true, then the given parameter values must also match the corresponding
589: * parameter sub-patterns. Note that setting this property to true will degrade performance.
590: * @since 1.1.0
591: */
592: public $matchValue;
593: /**
594: * @var string the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
595: * If this rule can match multiple verbs, please separate them with commas.
596: * If this property is not set, the rule can match any verb.
597: * Note that this property is only used when parsing a request. It is ignored for URL creation.
598: * @since 1.1.7
599: */
600: public $verb;
601: /**
602: * @var boolean whether this rule is only used for request parsing.
603: * Defaults to false, meaning the rule is used for both URL parsing and creation.
604: * @since 1.1.7
605: */
606: public $parsingOnly=false;
607: /**
608: * @var string the controller/action pair
609: */
610: public $route;
611: /**
612: * @var array the mapping from route param name to token name (e.g. _r1=><1>)
613: */
614: public $references=array();
615: /**
616: * @var string the pattern used to match route
617: */
618: public $routePattern;
619: /**
620: * @var string regular expression used to parse a URL
621: */
622: public $pattern;
623: /**
624: * @var string template used to construct a URL
625: */
626: public $template;
627: /**
628: * @var array list of parameters (name=>regular expression)
629: */
630: public $params=array();
631: /**
632: * @var boolean whether the URL allows additional parameters at the end of the path info.
633: */
634: public $append;
635: /**
636: * @var boolean whether host info should be considered for this rule
637: */
638: public $hasHostInfo;
639:
640: /**
641: * Constructor.
642: * @param string $route the route of the URL (controller/action)
643: * @param string $pattern the pattern for matching the URL
644: */
645: public function __construct($route,$pattern)
646: {
647: if(is_array($route))
648: {
649: foreach(array('urlSuffix', 'caseSensitive', 'defaultParams', 'matchValue', 'verb', 'parsingOnly') as $name)
650: {
651: if(isset($route[$name]))
652: $this->$name=$route[$name];
653: }
654: if(isset($route['pattern']))
655: $pattern=$route['pattern'];
656: $route=$route[0];
657: }
658: $this->route=trim($route,'/');
659:
660: $tr2['/']=$tr['/']='\\/';
661: $tr['.']='\\.';
662:
663: if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
664: {
665: foreach($matches2[1] as $name)
666: $this->references[$name]="<$name>";
667: }
668:
669: $this->hasHostInfo=!strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);
670:
671: if($this->verb!==null)
672: $this->verb=preg_split('/[\s,]+/',strtoupper($this->verb),-1,PREG_SPLIT_NO_EMPTY);
673:
674: if(preg_match_all('/<(\w+):?(.*?)?>/',$pattern,$matches))
675: {
676: $tokens=array_combine($matches[1],$matches[2]);
677: foreach($tokens as $name=>$value)
678: {
679: if($value==='')
680: $value='[^\/]+';
681: $tr["<$name>"]="(?P<$name>$value)";
682: if(isset($this->references[$name]))
683: $tr2["<$name>"]=$tr["<$name>"];
684: else
685: $this->params[$name]=$value;
686: }
687: }
688: $p=rtrim($pattern,'*');
689: $this->append=$p!==$pattern;
690: $p=trim($p,'/');
691: $this->template=preg_replace('/<(\w+):?.*?>/','<$1>',$p);
692: $this->pattern='/^'.strtr($this->template,$tr).'\/';
693: if($this->append)
694: $this->pattern.='/u';
695: else
696: $this->pattern.='$/u';
697:
698: if($this->references!==array())
699: $this->routePattern='/^'.strtr($this->route,$tr2).'$/u';
700:
701: if(YII_DEBUG && @preg_match($this->pattern,'test')===false)
702: throw new CException(Yii::t('yii','The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.',
703: array('{route}'=>$route,'{pattern}'=>$pattern)));
704: }
705:
706: /**
707: * Creates a URL based on this rule.
708: * @param CUrlManager $manager the manager
709: * @param string $route the route
710: * @param array $params list of parameters
711: * @param string $ampersand the token separating name-value pairs in the URL.
712: * @return mixed the constructed URL or false on error
713: */
714: public function createUrl($manager,$route,$params,$ampersand)
715: {
716: if($this->parsingOnly)
717: return false;
718:
719: if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
720: $case='';
721: else
722: $case='i';
723:
724: $tr=array();
725: if($route!==$this->route)
726: {
727: if($this->routePattern!==null && preg_match($this->routePattern.$case,$route,$matches))
728: {
729: foreach($this->references as $key=>$name)
730: $tr[$name]=$matches[$key];
731: }
732: else
733: return false;
734: }
735:
736: foreach($this->defaultParams as $key=>$value)
737: {
738: if(isset($params[$key]))
739: {
740: if($params[$key]==$value)
741: unset($params[$key]);
742: else
743: return false;
744: }
745: }
746:
747: foreach($this->params as $key=>$value)
748: if(!isset($params[$key]))
749: return false;
750:
751: if($manager->matchValue && $this->matchValue===null || $this->matchValue)
752: {
753: foreach($this->params as $key=>$value)
754: {
755: if(!preg_match('/\A'.$value.'\z/u'.$case,$params[$key]))
756: return false;
757: }
758: }
759:
760: foreach($this->params as $key=>$value)
761: {
762: $tr["<$key>"]=urlencode($params[$key]);
763: unset($params[$key]);
764: }
765:
766: $suffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
767:
768: $url=strtr($this->template,$tr);
769:
770: if($this->hasHostInfo)
771: {
772: $hostInfo=Yii::app()->getRequest()->getHostInfo();
773: if(stripos($url,$hostInfo)===0)
774: $url=substr($url,strlen($hostInfo));
775: }
776:
777: if(empty($params))
778: return $url!=='' ? $url.$suffix : $url;
779:
780: if($this->append)
781: $url.='/'.$manager->createPathInfo($params,'/','/').$suffix;
782: else
783: {
784: if($url!=='')
785: $url.=$suffix;
786: $url.='?'.$manager->createPathInfo($params,'=',$ampersand);
787: }
788:
789: return $url;
790: }
791:
792: /**
793: * Parses a URL based on this rule.
794: * @param CUrlManager $manager the URL manager
795: * @param CHttpRequest $request the request object
796: * @param string $pathInfo path info part of the URL
797: * @param string $rawPathInfo path info that contains the potential URL suffix
798: * @return mixed the route that consists of the controller ID and action ID or false on error
799: */
800: public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
801: {
802: if($this->verb!==null && !in_array($request->getRequestType(), $this->verb, true))
803: return false;
804:
805: if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
806: $case='';
807: else
808: $case='i';
809:
810: if($this->urlSuffix!==null)
811: $pathInfo=$manager->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
812:
813: // URL suffix required, but not found in the requested URL
814: if($manager->useStrictParsing && $pathInfo===$rawPathInfo)
815: {
816: $urlSuffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
817: if($urlSuffix!='' && $urlSuffix!=='/')
818: return false;
819: }
820:
821: if($this->hasHostInfo)
822: $pathInfo=strtolower($request->getHostInfo()).rtrim('/'.$pathInfo,'/');
823:
824: $pathInfo.='/';
825:
826: if(preg_match($this->pattern.$case,$pathInfo,$matches))
827: {
828: foreach($this->defaultParams as $name=>$value)
829: {
830: if(!isset($_GET[$name]))
831: $_REQUEST[$name]=$_GET[$name]=$value;
832: }
833: $tr=array();
834: foreach($matches as $key=>$value)
835: {
836: if(isset($this->references[$key]))
837: $tr[$this->references[$key]]=$value;
838: elseif(isset($this->params[$key]))
839: $_REQUEST[$key]=$_GET[$key]=$value;
840: }
841: if($pathInfo!==$matches[0]) // there're additional GET params
842: $manager->parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
843: if($this->routePattern!==null)
844: return strtr($this->route,$tr);
845: else
846: return $this->route;
847: }
848: else
849: return false;
850: }
851: }
852: