mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-04 07:14:06 +01:00
448 lines
12 KiB
PHP
448 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* CUrlManager class file
|
|
*
|
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
|
* @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:
|
|
* <ul>
|
|
* <li>'path' format: /path/to/EntryScript.php/name1/value1/name2/value2...</li>
|
|
* <li>'get' format: /path/to/EntryScript.php?name1=value1&name2=value2...</li>
|
|
* </ul>
|
|
*
|
|
* When using 'path' format, CUrlManager uses a set of {@link setRules rules} to:
|
|
* <ul>
|
|
* <li>parse the requested URL into a route ('ControllerID/ActionID') and GET parameters;</li>
|
|
* <li>create URLs based on the given route and GET parameters.</li>
|
|
* </ul>
|
|
*
|
|
* 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,
|
|
* <pre>
|
|
* array(
|
|
* 'articles'=>'article/list',
|
|
* 'article/<id:\d+>/*'=>'article/read',
|
|
* )
|
|
* </pre>
|
|
* Two rules are specified in the above:
|
|
* <ul>
|
|
* <li>The first rule says that if the user requests the URL '/path/to/index.php/articles',
|
|
* it should be treated as '/path/to/index.php/article/list'; and vice versa applies
|
|
* when constructing such a URL.</li>
|
|
* <li>The second rule contains a named parameter 'id' which is specified using
|
|
* the <ParamName:RegExp> syntax. It says that if the user requests the URL
|
|
* '/path/to/index.php/article/13', it should be treated as '/path/to/index.php/article/read?id=13';
|
|
* and vice versa applies when constructing such a URL.</li>
|
|
* </ul>
|
|
*
|
|
* CUrlManager is a default application component that may be accessed via
|
|
* {@link CWebApplication::getUrlManager()}.
|
|
*
|
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
|
* @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)
|
|
{
|
|
if("$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:
|
|
* <pre>
|
|
* ControllerID/ActionID/Name1/Value1/Name2/Value2...
|
|
* </pre>
|
|
* @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[urldecode($segs[$i])]=urldecode($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 <qiang.xue@gmail.com>
|
|
* @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 if("$value"!=='')
|
|
$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]=urldecode($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[urldecode($segs[$i])]=urldecode($segs[$i+1]);
|
|
}
|
|
return $this->route;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|