* @link http://www.yiiframework.com/ * @copyright Copyright © 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CUrlManager manages the URLs of Yii Web applications. * * It provides URL construction ({@link createUrl()}) as well as parsing ({@link parseUrl()}) functionality. * * URLs managed via CUrlManager can be in one of the following two formats, * by setting {@link setUrlFormat urlFormat} property: * * * When using 'path' format, CUrlManager uses a set of {@link setRules rules} to: * * * A rule consists of a route and a pattern. The latter is used by CUrlManager to determine * which rule is used for parsing/creating URLs. A pattern is meant to match the path info * part of a URL. It may contain named parameters using the syntax '<ParamName:RegExp>'. * * When parsing a URL, a matching rule will extract the named parameters from the path info * and put them into the $_GET variable; when creating a URL, a matching rule will extract * the named parameters from $_GET and put them into the path info part of the created URL. * * If a pattern ends with '/*', it means additional GET parameters may be appended to the path * info part of the URL; otherwise, the GET parameters can only appear in the query string part. * * To specify URL rules, set the {@link setRules rules) property as an array of rules (pattern=>route). * For example, *
 * array(
 *     'articles'=>'article/list',
 *     'article//*'=>'article/read',
 * )
 * 
* Two rules are specified in the above: * * * CUrlManager is a default application component that may be accessed via * {@link CWebApplication::getUrlManager()}. * * @author Qiang Xue * @version $Id$ * @package system.web * @since 1.0 */ class CUrlManager extends CApplicationComponent { const CACHE_KEY='CPhpMessageSource.CUrlManager.rules'; const GET_FORMAT='get'; const PATH_FORMAT='path'; /** * @var string the URL suffix used when in 'path' format. * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. Defaults to empty. */ public $urlSuffix=''; /** * @var boolean whether to show entry script name in the constructed URL. Defaults to true. */ public $showScriptName=true; /** * @var string the GET variable name for route. Defaults to 'r'. */ public $routeVar='r'; private $_urlFormat=self::GET_FORMAT; private $_rules=array(); private $_groups=array(); private $_baseUrl; /** * Initializes the application component. */ public function init() { parent::init(); $this->processRules(); } /** * Processes the URL rules. */ protected function processRules() { if(empty($this->_rules) || $this->getUrlFormat()===self::GET_FORMAT) return; if(($cache=Yii::app()->getCache())!==null) { $hash=md5(serialize($this->_rules)); if(($data=$cache->get(self::CACHE_KEY))!==false && isset($data[1]) && $data[1]===$hash) { $this->_groups=$data[0]; return; } } foreach($this->_rules as $pattern=>$route) $this->_groups[$route][]=new CUrlRule($route,$pattern); if($cache!==null) $cache->set(self::CACHE_KEY,array($this->_groups,$hash)); } /** * @return array the URL rules */ public function getRules() { return $this->_rules; } /** * Sets the URL rules. * @param array the URL rules (pattern=>route) */ public function setRules($value) { if($this->_rules===array()) $this->_rules=$value; else $this->_rules=array_merge($this->_rules,$value); } /** * Constructs a URL. * @param string the controller and the action (e.g. article/read) * @param array list of GET parameters (name=>value). Both the name and value will be URL-encoded. * @param string the token separating name-value pairs in the URL. Defaults to '&'. * @return string the constructed URL */ public function createUrl($route,$params=array(),$ampersand='&') { unset($params[$this->routeVar]); if(isset($this->_groups[$route])) { foreach($this->_groups[$route] as $rule) { if(($url=$rule->createUrl($params,$this->urlSuffix,$ampersand))!==false) return $this->getBaseUrl().'/'.$url; } } return $this->createUrlDefault($route,$params,$ampersand); } /** * Contructs a URL based on default settings. * @param string the controller and the action (e.g. article/read) * @param array list of GET parameters * @param string the token separating name-value pairs in the URL. * @return string the constructed URL */ protected function createUrlDefault($route,$params,$ampersand) { if($this->getUrlFormat()===self::PATH_FORMAT) { $url=rtrim($this->getBaseUrl().'/'.$route,'/'); foreach($params as $key=>$value) $url.='/'.urlencode($key).'/'.urlencode($value); return $url.$this->urlSuffix; } else { $pairs=$route!==''?array($this->routeVar.'='.$route):array(); foreach($params as $key=>$value) $pairs[]=urlencode($key).'='.urlencode($value); $baseUrl=$this->getBaseUrl(); if(!$this->showScriptName) $baseUrl.='/'; if(($query=implode($ampersand,$pairs))!=='') return $baseUrl.'?'.$query; else return $baseUrl; } } /** * Parses the user request. * @param CHttpRequest the request application component * @return string the route that consists of the controller ID and action ID */ public function parseUrl($request) { if($this->getUrlFormat()===self::PATH_FORMAT) { $pathInfo=$this->removeUrlSuffix($request->getPathInfo()); foreach($this->_groups as $rules) { foreach($rules as $rule) { if(($r=$rule->parseUrl($pathInfo))!==false) return isset($_GET[$this->routeVar])?$_GET[$this->routeVar]:$r; } } return $this->parseUrlDefault($pathInfo); } else if(isset($_GET[$this->routeVar])) return $_GET[$this->routeVar]; else return isset($_POST[$this->routeVar])?$_POST[$this->routeVar]:''; } /** * Removes the URL suffix from path info. * @param string path info part in the URL * @return string path info with URL suffix removed. */ protected function removeUrlSuffix($pathInfo) { if(($ext=$this->urlSuffix)!=='' && substr($pathInfo,-strlen($ext))===$ext) return substr($pathInfo,0,-strlen($ext)); else return $pathInfo; } /** * Parses the URL using the default implementation. * This method is called only when the URL format is 'get' * and no appropriate rules can recognize the URL. * It assumes the path info of the URL is of the following format: *
	 * ControllerID/ActionID/Name1/Value1/Name2/Value2...
	 * 
* @param string path info part of the request URL * @return string the route that consists of the controller ID and action ID */ protected function parseUrlDefault($pathInfo) { $segs=explode('/',$pathInfo.'/'); $n=count($segs); for($i=2;$i<$n-1;$i+=2) $_GET[$segs[$i]]=$segs[$i+1]; return $segs[0].'/'.$segs[1]; } /** * @return string the base URL of the application (the part after host name and before query string). * If {@link showScriptName} is true, it will include the script name part. * Otherwise, it will not, and the ending slashes are stripped off. */ public function getBaseUrl() { if($this->_baseUrl!==null) return $this->_baseUrl; else { if($this->showScriptName) $this->_baseUrl=Yii::app()->getRequest()->getScriptUrl(); else $this->_baseUrl=Yii::app()->getRequest()->getBaseUrl(); return $this->_baseUrl; } } /** * @return string the URL format. Defaults to 'path'. */ public function getUrlFormat() { return $this->_urlFormat; } /** * @param string the URL format. It must be either 'path' or 'get'. */ public function setUrlFormat($value) { if($value===self::PATH_FORMAT || $value===self::GET_FORMAT) $this->_urlFormat=$value; else throw new CException(Yii::t('yii#CUrlManager.UrlFormat must be either "path" or "get".')); } } /** * CUrlRule represents a URL formatting/parsing rule. * * It mainly consists of two parts: route and pattern. The former classifies * the rule so that it only applies to specific controller-action route. * The latter performs the actual formatting and parsing role. The pattern * may have a set of named parameters each of specific format. * * @author Qiang Xue * @version $Id$ * @package system.web * @since 1.0 */ class CUrlRule extends CComponent { /** * @var string the controller/action pair */ public $route; /** * @var string regular expression used to parse a URL */ public $pattern; /** * @var string template used to construct a URL */ public $template; /** * @var array list of parameters (name=>regular expression) */ public $params; /** * @var boolean whether the URL allows additional parameters at the end of the path info. */ public $append; /** * @var string a token identifies the rule to a certain degree */ public $signature; /** * Constructor. * @param string the route of the URL (controller/action) * @param string the pattern for matching the URL */ public function __construct($route,$pattern) { $this->route=$route; if(preg_match_all('/<(\w+):?(.*?)?>/',$pattern,$matches)) $this->params=array_combine($matches[1],$matches[2]); else $this->params=array(); $p=rtrim($pattern,'*'); $this->append=$p!==$pattern; $p=trim($p,'/'); $this->template=preg_replace('/<(\w+):?.*?>/','<$1>',$p); if(($pos=strpos($p,'<'))!==false) $this->signature=substr($p,0,$pos); else $this->signature=$p; $tr['/']='\\/'; foreach($this->params as $key=>$value) $tr["<$key>"]="(?P<$key>".($value!==''?$value:'[^\/]+').')'; $this->pattern='/^'.strtr($this->template,$tr).'\/'; if($this->append) $this->pattern.='/u'; else $this->pattern.='$/u'; if(@preg_match($this->pattern,'test')===false) throw new CException(Yii::t('yii#The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.', array('{route}'=>$route,'{pattern}'=>$pattern))); } /** * @param array list of parameters * @param string URL suffix * @param string the token separating name-value pairs in the URL. * @return string the constructed URL */ public function createUrl($params,$suffix,$ampersand) { foreach($this->params as $key=>$value) { if(!isset($params[$key])) return false; } $tr=array(); $rest=array(); $sep=$this->append?'/':'='; foreach($params as $key=>$value) { if(isset($this->params[$key])) $tr["<$key>"]=$value; else $rest[]=urlencode($key).$sep.urlencode($value); } $url=strtr($this->template,$tr); if($rest===array()) return $url!=='' ? $url.$suffix : $url; else { if($this->append) { $url.='/'.implode('/',$rest); if($url!=='') $url.=$suffix; } else { if($url!=='') $url.=$suffix; $url.='?'.implode($ampersand,$rest); } return $url; } } /** * @param string path info part of the URL * @return string the route that consists of the controller ID and action ID */ public function parseUrl($pathInfo) { if(strncmp($pathInfo,$this->signature,strlen($this->signature))) return false; $pathInfo.='/'; if(preg_match($this->pattern,$pathInfo,$matches)) { foreach($matches as $key=>$value) { if(is_string($key)) $_GET[$key]=$value; } if($pathInfo!==$matches[0]) { $segs=explode('/',ltrim(substr($pathInfo,strlen($matches[0])),'/')); $n=count($segs); for($i=0;$i<$n-1;$i+=2) $_GET[$segs[$i]]=$segs[$i+1]; } return $this->route; } else return false; } }