PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/web/CUrlManager.php

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