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 */ 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