Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TUrlMapping and TUrlMappingPattern class file.** @author Wei Zhuo <weizhuo[at]gamil[dot]com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TUrlMapping.php 2584 2008-12-04 17:05:12Z haertl.mike $* @package System.Web*/Prado::using('System.Web.TUrlManager');Prado::using('System.Collections.TAttributeCollection');/*** TUrlMapping Class** The TUrlMapping module allows PRADO to construct and recognize URLs* based on specific patterns.** TUrlMapping consists of a list of URL patterns which are used to match* against the currently requested URL. The first matching pattern will then* be used to decompose the URL into request parameters (accessible through* <code>$this->Request['paramname']</code>).** The patterns can also be used to construct customized URLs. In this case,* the parameters in an applied pattern will be replaced with the corresponding* GET variable values.** Since it is derived from {@link TUrlManager}, it should be configured globally* in the application configuration like the following,* <code>* <module id="request" class="THttpRequest" UrlManager="friendly-url" />* <module id="friendly-url" class="System.Web.TUrlMapping" EnableCustomUrl="true">* <url ServiceParameter="Posts.ViewPost" pattern="post/{id}/" parameters.id="\d+" />* <url ServiceParameter="Posts.ListPost" pattern="archive/{time}/" parameters.time="\d{6}" />* <url ServiceParameter="Posts.ListPost" pattern="category/{cat}/" parameters.cat="\d+" />* </module>* </code>** In the above, each <tt><url></tt> element specifies a URL pattern represented* as a {@link TUrlMappingPattern} internally. You may create your own pattern classes* by extending {@link TUrlMappingPattern} and specifying the <tt><class></tt> attribute* in the element.** The patterns can be also be specified in an external file using the {@link setConfigFile ConfigFile} property.** The URL mapping are evaluated in order, only the first mapping that matches* the URL will be used. Cascaded mapping can be achieved by placing the URL mappings* in particular order. For example, placing the most specific mappings first.** Only the PATH_INFO part of the URL is used to match the available patterns. The matching* is strict in the sense that the whole pattern must match the whole PATH_INFO of the URL.** From PRADO v3.1.1, TUrlMapping also provides support for constructing URLs according to* the specified pattern. You may enable this functionality by setting {@link setEnableCustomUrl EnableCustomUrl} to true.* When you call THttpRequest::constructUrl() (or via TPageService::constructUrl()),* TUrlMapping will examine the available URL mapping patterns using their {@link TUrlMappingPattern::getServiceParameter ServiceParameter}* and {@link TUrlMappingPattern::getPattern Pattern} properties. A pattern is applied if its* {@link TUrlMappingPattern::getServiceParameter ServiceParameter} matches the service parameter passed* to constructUrl() and every parameter in the {@link getPattern Pattern} is found* in the GET variables.** @author Wei Zhuo <weizhuo[at]gmail[dot]com>* @version $Id: TUrlMapping.php 2584 2008-12-04 17:05:12Z haertl.mike $* @package System.Web* @since 3.0.5*/class TUrlMapping extends TUrlManager{/*** File extension of external configuration file*/const CONFIG_FILE_EXT='.xml';/*** @var TUrlMappingPattern[] list of patterns.*/protected $_patterns=array();/*** @var TUrlMappingPattern matched pattern.*/private $_matched;/*** @var string external configuration file*/private $_configFile=null;/*** @var boolean whether to enable custom contructUrl*/private $_customUrl=false;/*** @var array rules for constructing URLs*/protected $_constructRules=array();private $_urlPrefix='';private $_defaultMappingClass='TUrlMappingPattern';/*** Initializes this module.* This method is required by the IModule interface.* @param TXmlElement configuration for this module, can be null* @throws TConfigurationException if module is configured in the global scope.*/public function init($xml){parent::init($xml);if($this->getRequest()->getRequestResolved())throw new TConfigurationException('urlmapping_global_required');if($this->_configFile!==null)$this->loadConfigFile();$this->loadUrlMappings($xml);if($this->_urlPrefix==='')$this->_urlPrefix=$this->getRequest()->getApplicationUrl();$this->_urlPrefix=rtrim($this->_urlPrefix,'/');}/*** Initialize the module from configuration file.* @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.*/protected function loadConfigFile(){if(is_file($this->_configFile)){$dom=new TXmlDocument;$dom->loadFromFile($this->_configFile);$this->loadUrlMappings($dom);}elsethrow new TConfigurationException('urlmapping_configfile_inexistent',$this->_configFile);}/*** Returns a value indicating whether to enable custom constructUrl.* If true, constructUrl() will make use of the URL mapping rules to* construct valid URLs.* @return boolean whether to enable custom constructUrl. Defaults to false.* @since 3.1.1*/public function getEnableCustomUrl(){return $this->_customUrl;}/*** Sets a value indicating whether to enable custom constructUrl.* If true, constructUrl() will make use of the URL mapping rules to* construct valid URLs.* @param boolean whether to enable custom constructUrl.* @since 3.1.1*/public function setEnableCustomUrl($value){$this->_customUrl=TPropertyValue::ensureBoolean($value);}/*** @return string the part that will be prefixed to the constructed URLs. Defaults to the requested script path (e.g. /path/to/index.php for a URL http://hostname/path/to/index.php)* @since 3.1.1*/public function getUrlPrefix(){return $this->_urlPrefix;}/*** @param string the part that will be prefixed to the constructed URLs. This is used by constructUrl() when EnableCustomUrl is set true.* @see getUrlPrefix* @since 3.1.1*/public function setUrlPrefix($value){$this->_urlPrefix=$value;}/*** @return string external configuration file. Defaults to null.*/public function getConfigFile(){return $this->_configFile;}/*** @param string external configuration file in namespace format. The file* must be suffixed with '.xml'.* @throws TInvalidDataValueException if the file is invalid.*/public function setConfigFile($value){if(($this->_configFile=Prado::getPathOfNamespace($value,self::CONFIG_FILE_EXT))===null)throw new TConfigurationException('urlmapping_configfile_invalid',$value);}/*** @return string the default class of URL mapping patterns. Defaults to TUrlMappingPattern.* @since 3.1.1*/public function getDefaultMappingClass(){return $this->_defaultMappingClass;}/*** Sets the default class of URL mapping patterns.* When a URL matching pattern does not specify "class" attribute, it will default to the class* specified by this property. You may use either a class name or a namespace format of class (if the class needs to be included first.)* @param string the default class of URL mapping patterns.* @since 3.1.1*/public function setDefaultMappingClass($value){$this->_defaultMappingClass=$value;}/*** Load and configure each url mapping pattern.* @param TXmlElement configuration node* @throws TConfigurationException if specific pattern class is invalid*/protected function loadUrlMappings($xml){foreach($xml->getElementsByTagName('url') as $url){$properties=$url->getAttributes();if(($class=$properties->remove('class'))===null)$class=$this->getDefaultMappingClass();$pattern=Prado::createComponent($class,$this);if(!($pattern instanceof TUrlMappingPattern))throw new TConfigurationException('urlmapping_urlmappingpattern_required');foreach($properties as $name=>$value)$pattern->setSubproperty($name,$value);$this->_patterns[]=$pattern;$pattern->init($url);$key=$pattern->getServiceID().':'.$pattern->getServiceParameter();$this->_constructRules[$key][]=$pattern;}}/*** Parses the request URL and returns an array of input parameters.* This method overrides the parent implementation.* The input parameters do not include GET and POST variables.* This method uses the request URL path to find the first matching pattern. If found* the matched pattern parameters are used to return as the input parameters.* @return array list of input parameters*/public function parseUrl(){$request=$this->getRequest();foreach($this->_patterns as $pattern){$matches=$pattern->getPatternMatches($request);if(count($matches)>0){$this->_matched=$pattern;$params=array();foreach($matches as $key=>$value){if(is_string($key))$params[$key]=$value;}if (!$pattern->getIsWildCardPattern())$params[$pattern->getServiceID()]=$pattern->getServiceParameter();return $params;}}return parent::parseUrl();}/*** Constructs a URL that can be recognized by PRADO.** This method provides the actual implementation used by {@link THttpRequest::constructUrl}.* Override this method if you want to provide your own way of URL formatting.* If you do so, you may also need to override {@link parseUrl} so that the URL can be properly parsed.** The URL is constructed as the following format:* /entryscript.php?serviceID=serviceParameter&get1=value1&...* If {@link THttpRequest::setUrlFormat THttpRequest.UrlFormat} is 'Path',* the following format is used instead:* /entryscript.php/serviceID/serviceParameter/get1,value1/get2,value2...* @param string service ID* @param string service parameter* @param array GET parameters, null if not provided* @param boolean whether to encode the ampersand in URL* @param boolean whether to encode the GET parameters (their names and values)* @return string URL* @see parseUrl* @since 3.1.1*/public function constructUrl($serviceID,$serviceParam,$getItems,$encodeAmpersand,$encodeGetItems){if($this->_customUrl){if(!(is_array($getItems) || ($getItems instanceof Traversable)))$getItems=array();$key=$serviceID.':'.$serviceParam;$wildCardKey = ($pos=strrpos($serviceParam,'.'))!==false ?$serviceID.':'.substr($serviceParam,0,$pos).'.*' : $serviceID.':*';if(isset($this->_constructRules[$key])){foreach($this->_constructRules[$key] as $rule){if($rule->supportCustomUrl($getItems))return $rule->constructUrl($getItems,$encodeAmpersand,$encodeGetItems);}}elseif(isset($this->_constructRules[$wildCardKey])){foreach($this->_constructRules[$wildCardKey] as $rule){if($rule->supportCustomUrl($getItems)){$getItems['*']= $pos ? substr($serviceParam,$pos+1) : $serviceParam;return $rule->constructUrl($getItems,$encodeAmpersand,$encodeGetItems);}}}}return parent::constructUrl($serviceID,$serviceParam,$getItems,$encodeAmpersand,$encodeGetItems);}/*** @return TUrlMappingPattern the matched pattern, null if not found.*/public function getMatchingPattern(){return $this->_matched;}}/*** TUrlMappingPattern class.** TUrlMappingPattern represents a pattern used to parse and construct URLs.* If the currently requested URL matches the pattern, it will alter* the THttpRequest parameters. If a constructUrl() call matches the pattern* parameters, the pattern will generate a valid URL. In both case, only the PATH_INFO* part of a URL is parsed/constructed using the pattern.** To specify the pattern, set the {@link setPattern Pattern} property.* {@link setPattern Pattern} takes a string expression with* parameter names enclosed between a left brace '{' and a right brace '}'.* The patterns for each parameter can be set using {@link getParameters Parameters}* attribute collection. For example* <code>* <url ... pattern="articles/{year}/{month}/{day}"* parameters.year="\d{4}" parameters.month="\d{2}" parameters.day="\d+" />* </code>** In the above example, the pattern contains 3 parameters named "year",* "month" and "day". The pattern for these parameters are, respectively,* "\d{4}" (4 digits), "\d{2}" (2 digits) and "\d+" (1 or more digits).* Essentially, the <tt>Parameters</tt> attribute name and values are used* as substrings in replacing the placeholders in the <tt>Pattern</tt> string* to form a complete regular expression string.** For more complicated patterns, one may specify the pattern using a regular expression* by {@link setRegularExpression RegularExpression}. For example, the above pattern* is equivalent to the following regular expression-based pattern:* <code>* /^articles\/(?P<year>\d{4})\/(?P<month>\d{2})\/(?P<day>\d+)$/u* </code>* The above regular expression used the "named group" feature available in PHP.* Notice that you need to escape the slash in regular expressions.** Thus, only an url that matches the pattern will be valid. For example,* a URL <tt>http://example.com/index.php/articles/2006/07/21</tt> will match the above pattern,* while <tt>http://example.com/index.php/articles/2006/07/hello</tt> will not* since the "day" parameter pattern is not satisfied.** The parameter values are available through the <tt>THttpRequest</tt> instance (e.g.* <tt>$this->Request['year']</tt>).** The {@link setServiceParameter ServiceParameter} and {@link setServiceID ServiceID}* (the default ID is 'page') set the service parameter and service id respectively.** Since 3.1.4 you can also use simplyfied wildcard patterns to match multiple* ServiceParameters with a single rule. The pattern must contain the placeholder* {*} for the ServiceParameter. For example** <url ServiceParameter="adminpages.*" pattern="admin/{*}" />** This rule will match an URL like <tt>http://example.com/index.php/admin/edituser</tt>* and resolve it to the page Application.pages.admin.edituser. The wildcard matching* is non-recursive. That means you have to add a rule for every subdirectory you* want to access pages in:** <url ServiceParameter="adminpages.users.*" pattern="useradmin/{*}" />** It is still possible to define an explicit rule for a page in the wildcard path.* This rule has to preceed the wildcard rule.** You can also use parameters with wildcard patterns. The parameters are then* available with every matching page:** <url ServiceParameter="adminpages.*" pattern="admin/{*}/{id}" parameters.id="\d+" />** To enable automatic parameter encoding in a path format fro wildcard patterns you can set* {@setUrlFormat UrlFormat} to 'Path':** <url ServiceParameter="adminpages.*" pattern="admin/{*}" UrlFormat="Path" />** This will create and parse URLs of the form* <tt>.../index.php/admin/listuser/param1/value1/param2/value2</tt>.** Use {@setUrlParamSeparator} to define another separator character between parameter* name and value. Parameter/value pairs are always separated by a '/'.** <url ServiceParameter="adminpages.*" pattern="admin/{*}" UrlFormat="Path" UrlParamSeparator="-" />** <tt>.../index.php/admin/listuser/param1-value1/param2-value2</tt>.** @author Wei Zhuo <weizhuo[at]gmail[dot]com>* @version $Id: TUrlMapping.php 2584 2008-12-04 17:05:12Z haertl.mike $* @package System.Web* @since 3.0.5*/class TUrlMappingPattern extends TComponent{/*** @var string service parameter such as Page class name.*/private $_serviceParameter;/*** @var string service ID, default is 'page'.*/private $_serviceID='page';/*** @var string url pattern to match.*/private $_pattern;/*** @var TMap parameter regular expressions.*/private $_parameters;/*** @var string regular expression pattern.*/private $_regexp='';private $_customUrl=true;private $_manager;private $_caseSensitive=true;private $_isWildCardPattern=false;private $_urlFormat=THttpRequestUrlFormat::Get;private $_separator='/';/*** Constructor.* @param TUrlManager the URL manager instance*/public function __construct(TUrlManager $manager){$this->_manager=$manager;$this->_parameters=new TAttributeCollection;$this->_parameters->setCaseSensitive(true);}/*** @return TUrlManager the URL manager instance*/public function getManager(){return $this->_manager;}/*** Initializes the pattern.* @param TXmlElement configuration for this module.* @throws TConfigurationException if service parameter is not specified*/public function init($config){if($this->_serviceParameter===null)throw new TConfigurationException('urlmappingpattern_serviceparameter_required', $this->getPattern());if(strpos($this->_serviceParameter,'*')!==false)$this->_isWildCardPattern=true;}/*** Substitute the parameter key value pairs as named groupings* in the regular expression matching pattern.* @return string regular expression pattern with parameter subsitution*/protected function getParameterizedPattern(){$params=array();$values=array();foreach($this->_parameters as $key=>$value){$params[]='{'.$key.'}';$values[]='(?P<'.$key.'>'.$value.')';}if ($this->getIsWildCardPattern()) {$params[]='{*}';// service parameter must not contain '=' and '/'$values[]='(?P<'.$this->getServiceID().'>[^=/]+)';}$params[]='/';$values[]='\\/';$regexp=str_replace($params,$values,trim($this->getPattern(),'/').'/');if ($this->_urlFormat===THttpRequestUrlFormat::Get)$regexp='/^'.$regexp.'$/u';else$regexp='/^'.$regexp.'(?P<urlparams>.*)$/u';if(!$this->getCaseSensitive())$regexp.='i';return $regexp;}/*** @return string full regular expression mapping pattern*/public function getRegularExpression(){return $this->_regexp;}/*** @param string full regular expression mapping pattern.*/public function setRegularExpression($value){$this->_regexp=$value;}/*** @return boolean whether the {@link getPattern Pattern} should be treated as case sensititve. Defaults to true.*/public function getCaseSensitive(){return $this->_caseSensitive;}/*** @param boolean whether the {@link getPattern Pattern} should be treated as case sensititve.*/public function setCaseSensitive($value){$this->_caseSensitive=TPropertyValue::ensureBoolean($value);}/*** @param string service parameter, such as page class name.*/public function setServiceParameter($value){$this->_serviceParameter=$value;}/*** @return string service parameter, such as page class name.*/public function getServiceParameter(){return $this->_serviceParameter;}/*** @param string service id to handle.*/public function setServiceID($value){$this->_serviceID=$value;}/*** @return string service id.*/public function getServiceID(){return $this->_serviceID;}/*** @return string url pattern to match. Defaults to ''.*/public function getPattern(){return $this->_pattern;}/*** @param string url pattern to match.*/public function setPattern($value){$this->_pattern = $value;}/*** @return TAttributeCollection parameter key value pairs.*/public function getParameters(){return $this->_parameters;}/*** @param TAttributeCollection new parameter key value pairs.*/public function setParameters($value){$this->_parameters=$value;}/*** Uses URL pattern (or full regular expression if available) to* match the given url path.* @param THttpRequest the request module* @return array matched parameters, empty if no matches.*/public function getPatternMatches($request){$matches=array();if(($pattern=$this->getRegularExpression())!=='')preg_match($pattern,$request->getPathInfo(),$matches);elsepreg_match($this->getParameterizedPattern(),trim($request->getPathInfo(),'/').'/',$matches);if (isset($matches['urlparams'])){$params=explode('/',$matches['urlparams']);if ($this->_separator==='/'){while($key=array_shift($params))$matches2[$key]=($value=array_shift($params)) ? $value : '';}else{array_pop($params);foreach($params as $param){list($key,$value)=explode($this->_separator,$param,2);$matches[$key]=$value;}}unset($matches['urlparams']);}return $matches;}/*** Returns a value indicating whether to use this pattern to construct URL.* @return boolean whether to enable custom constructUrl. Defaults to true.* @since 3.1.1*/public function getEnableCustomUrl(){return $this->_customUrl;}/*** Sets a value indicating whether to enable custom constructUrl using this pattern* @param boolean whether to enable custom constructUrl.*/public function setEnableCustomUrl($value){$this->_customUrl=TPropertyValue::ensureBoolean($value);}/*** @return boolean whether this pattern is a wildcard pattern* @since 3.1.4*/public function getIsWildCardPattern() {return $this->_isWildCardPattern;}/*** @return THttpRequestUrlFormat the format of URLs. Defaults to THttpRequestUrlFormat::Get.*/public function getUrlFormat(){return $this->_urlFormat;}/*** Sets the format of URLs constructed and interpreted by this pattern.* A Get URL format is like index.php?name1=value1&name2=value2* while a Path URL format is like index.php/name1/value1/name2/value.* The separating character between name and value can be configured with* {@link setUrlParamSeparator} and defaults to '/'.* Changing the UrlFormat will affect {@link constructUrl} and how GET variables* are parsed.* @param THttpRequestUrlFormat the format of URLs.* @param since 3.1.4*/public function setUrlFormat($value){$this->_urlFormat=TPropertyValue::ensureEnum($value,'THttpRequestUrlFormat');}/*** @return string separator used to separate GET variable name and value when URL format is Path. Defaults to slash '/'.*/public function getUrlParamSeparator(){return $this->_separator;}/*** @param string separator used to separate GET variable name and value when URL format is Path.* @throws TInvalidDataValueException if the separator is not a single character*/public function setUrlParamSeparator($value){if(strlen($value)===1)$this->_separator=$value;elsethrow new TInvalidDataValueException('httprequest_separator_invalid');}/*** @param array list of GET items to be put in the constructed URL* @return boolean whether this pattern IS the one for constructing the URL with the specified GET items.* @since 3.1.1*/public function supportCustomUrl($getItems){if(!$this->_customUrl || $this->getPattern()===null)return false;foreach($this->_parameters as $key=>$value){if(!isset($getItems[$key]))return false;}return true;}/*** Constructs a URL using this pattern.* @param array list of GET variables* @param boolean whether the ampersand should be encoded in the constructed URL* @param boolean whether the GET variables should be encoded in the constructed URL* @return string the constructed URL* @since 3.1.1*/public function constructUrl($getItems,$encodeAmpersand,$encodeGetItems){$extra=array();$replace=array();// for the GET variables matching the pattern, put them in the URL pathforeach($getItems as $key=>$value){if($this->_parameters->contains($key) || $key==='*' && $this->getIsWildCardPattern())$replace['{'.$key.'}']=$encodeGetItems ? rawurlencode($value) : $value;else$extra[$key]=$value;}$url=$this->_manager->getUrlPrefix().'/'.ltrim(strtr($this->getPattern(),$replace),'/');// for the rest of the GET variables, put them in the query stringif(count($extra)>0){if ($this->_urlFormat===THttpRequestUrlFormat::Path && $this->getIsWildCardPattern()) {foreach ($extra as $name=>$value)$url.='/'.$name.$this->_separator.($encodeGetItems?rawurlencode($value):$value);return $url;}$url2='';$amp=$encodeAmpersand?'&':'&';if($encodeGetItems){foreach($extra as $name=>$value){if(is_array($value)){$name=rawurlencode($name.'[]');foreach($value as $v)$url2.=$amp.$name.'='.rawurlencode($v);}else$url2.=$amp.rawurlencode($name).'='.rawurlencode($value);}}else{foreach($extra as $name=>$value){if(is_array($value)){foreach($value as $v)$url2.=$amp.$name.'[]='.$v;}else$url2.=$amp.$name.'='.$value;}}$url=$url.'?'.substr($url2,strlen($amp));}return $url;}}