web/lib/Zend/Controller/Router/Route.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Controller
       
    17  * @subpackage Router
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @version    $Id: Route.php 20096 2010-01-06 02:05:09Z bkarwin $
       
    20  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    21  */
       
    22 
       
    23 /** Zend_Controller_Router_Route_Abstract */
       
    24 require_once 'Zend/Controller/Router/Route/Abstract.php';
       
    25 
       
    26 /**
       
    27  * Route
       
    28  *
       
    29  * @package    Zend_Controller
       
    30  * @subpackage Router
       
    31  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    32  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    33  * @see        http://manuals.rubyonrails.com/read/chapter/65
       
    34  */
       
    35 class Zend_Controller_Router_Route extends Zend_Controller_Router_Route_Abstract
       
    36 {
       
    37     /**
       
    38      * Default translator
       
    39      *
       
    40      * @var Zend_Translate
       
    41      */
       
    42     protected static $_defaultTranslator;
       
    43 
       
    44     /**
       
    45      * Translator
       
    46      *
       
    47      * @var Zend_Translate
       
    48      */
       
    49     protected $_translator;
       
    50 
       
    51     /**
       
    52      * Default locale
       
    53      *
       
    54      * @var mixed
       
    55      */
       
    56     protected static $_defaultLocale;
       
    57 
       
    58     /**
       
    59      * Locale
       
    60      *
       
    61      * @var mixed
       
    62      */
       
    63     protected $_locale;
       
    64 
       
    65     /**
       
    66      * Wether this is a translated route or not
       
    67      *
       
    68      * @var boolean
       
    69      */
       
    70     protected $_isTranslated = false;
       
    71 
       
    72     /**
       
    73      * Translatable variables
       
    74      *
       
    75      * @var array
       
    76      */
       
    77     protected $_translatable = array();
       
    78 
       
    79     protected $_urlVariable = ':';
       
    80     protected $_urlDelimiter = '/';
       
    81     protected $_regexDelimiter = '#';
       
    82     protected $_defaultRegex = null;
       
    83 
       
    84     /**
       
    85      * Holds names of all route's pattern variable names. Array index holds a position in URL.
       
    86      * @var array
       
    87      */
       
    88     protected $_variables = array();
       
    89 
       
    90     /**
       
    91      * Holds Route patterns for all URL parts. In case of a variable it stores it's regex
       
    92      * requirement or null. In case of a static part, it holds only it's direct value.
       
    93      * In case of a wildcard, it stores an asterisk (*)
       
    94      * @var array
       
    95      */
       
    96     protected $_parts = array();
       
    97 
       
    98     /**
       
    99      * Holds user submitted default values for route's variables. Name and value pairs.
       
   100      * @var array
       
   101      */
       
   102     protected $_defaults = array();
       
   103 
       
   104     /**
       
   105      * Holds user submitted regular expression patterns for route's variables' values.
       
   106      * Name and value pairs.
       
   107      * @var array
       
   108      */
       
   109     protected $_requirements = array();
       
   110 
       
   111     /**
       
   112      * Associative array filled on match() that holds matched path values
       
   113      * for given variable names.
       
   114      * @var array
       
   115      */
       
   116     protected $_values = array();
       
   117 
       
   118     /**
       
   119      * Associative array filled on match() that holds wildcard variable
       
   120      * names and values.
       
   121      * @var array
       
   122      */
       
   123     protected $_wildcardData = array();
       
   124 
       
   125     /**
       
   126      * Helper var that holds a count of route pattern's static parts
       
   127      * for validation
       
   128      * @var int
       
   129      */
       
   130     protected $_staticCount = 0;
       
   131 
       
   132     public function getVersion() {
       
   133         return 1;
       
   134     }
       
   135 
       
   136     /**
       
   137      * Instantiates route based on passed Zend_Config structure
       
   138      *
       
   139      * @param Zend_Config $config Configuration object
       
   140      */
       
   141     public static function getInstance(Zend_Config $config)
       
   142     {
       
   143         $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array();
       
   144         $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
       
   145         return new self($config->route, $defs, $reqs);
       
   146     }
       
   147 
       
   148     /**
       
   149      * Prepares the route for mapping by splitting (exploding) it
       
   150      * to a corresponding atomic parts. These parts are assigned
       
   151      * a position which is later used for matching and preparing values.
       
   152      *
       
   153      * @param string $route Map used to match with later submitted URL path
       
   154      * @param array $defaults Defaults for map variables with keys as variable names
       
   155      * @param array $reqs Regular expression requirements for variables (keys as variable names)
       
   156      * @param Zend_Translate $translator Translator to use for this instance
       
   157      */
       
   158     public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null)
       
   159     {
       
   160         $route               = trim($route, $this->_urlDelimiter);
       
   161         $this->_defaults     = (array) $defaults;
       
   162         $this->_requirements = (array) $reqs;
       
   163         $this->_translator   = $translator;
       
   164         $this->_locale       = $locale;
       
   165 
       
   166         if ($route !== '') {
       
   167             foreach (explode($this->_urlDelimiter, $route) as $pos => $part) {
       
   168                 if (substr($part, 0, 1) == $this->_urlVariable && substr($part, 1, 1) != $this->_urlVariable) {
       
   169                     $name = substr($part, 1);
       
   170 
       
   171                     if (substr($name, 0, 1) === '@' && substr($name, 1, 1) !== '@') {
       
   172                         $name                  = substr($name, 1);
       
   173                         $this->_translatable[] = $name;
       
   174                         $this->_isTranslated   = true;
       
   175                     }
       
   176 
       
   177                     $this->_parts[$pos]     = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex);
       
   178                     $this->_variables[$pos] = $name;
       
   179                 } else {
       
   180                     if (substr($part, 0, 1) == $this->_urlVariable) {
       
   181                         $part = substr($part, 1);
       
   182                     }
       
   183 
       
   184                     if (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@') {
       
   185                         $this->_isTranslated = true;
       
   186                     }
       
   187 
       
   188                     $this->_parts[$pos] = $part;
       
   189 
       
   190                     if ($part !== '*') {
       
   191                         $this->_staticCount++;
       
   192                     }
       
   193                 }
       
   194             }
       
   195         }
       
   196     }
       
   197 
       
   198     /**
       
   199      * Matches a user submitted path with parts defined by a map. Assigns and
       
   200      * returns an array of variables on a successful match.
       
   201      *
       
   202      * @param string $path Path used to match against this routing map
       
   203      * @return array|false An array of assigned values or a false on a mismatch
       
   204      */
       
   205     public function match($path, $partial = false)
       
   206     {
       
   207         if ($this->_isTranslated) {
       
   208             $translateMessages = $this->getTranslator()->getMessages();
       
   209         }
       
   210 
       
   211         $pathStaticCount = 0;
       
   212         $values          = array();
       
   213         $matchedPath     = '';
       
   214 
       
   215         if (!$partial) {
       
   216             $path = trim($path, $this->_urlDelimiter);
       
   217         }
       
   218 
       
   219         if ($path !== '') {
       
   220             $path = explode($this->_urlDelimiter, $path);
       
   221 
       
   222             foreach ($path as $pos => $pathPart) {
       
   223                 // Path is longer than a route, it's not a match
       
   224                 if (!array_key_exists($pos, $this->_parts)) {
       
   225                     if ($partial) {
       
   226                         break;
       
   227                     } else {
       
   228                         return false;
       
   229                     }
       
   230                 }
       
   231 
       
   232                 $matchedPath .= $pathPart . $this->_urlDelimiter;
       
   233 
       
   234                 // If it's a wildcard, get the rest of URL as wildcard data and stop matching
       
   235                 if ($this->_parts[$pos] == '*') {
       
   236                     $count = count($path);
       
   237                     for($i = $pos; $i < $count; $i+=2) {
       
   238                         $var = urldecode($path[$i]);
       
   239                         if (!isset($this->_wildcardData[$var]) && !isset($this->_defaults[$var]) && !isset($values[$var])) {
       
   240                             $this->_wildcardData[$var] = (isset($path[$i+1])) ? urldecode($path[$i+1]) : null;
       
   241                         }
       
   242                     }
       
   243 
       
   244                     $matchedPath = implode($this->_urlDelimiter, $path);
       
   245                     break;
       
   246                 }
       
   247 
       
   248                 $name     = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null;
       
   249                 $pathPart = urldecode($pathPart);
       
   250 
       
   251                 // Translate value if required
       
   252                 $part = $this->_parts[$pos];
       
   253                 if ($this->_isTranslated && (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@' && $name === null) || $name !== null && in_array($name, $this->_translatable)) {
       
   254                     if (substr($part, 0, 1) === '@') {
       
   255                         $part = substr($part, 1);
       
   256                     }
       
   257 
       
   258                     if (($originalPathPart = array_search($pathPart, $translateMessages)) !== false) {
       
   259                         $pathPart = $originalPathPart;
       
   260                     }
       
   261                 }
       
   262 
       
   263                 if (substr($part, 0, 2) === '@@') {
       
   264                     $part = substr($part, 1);
       
   265                 }
       
   266 
       
   267                 // If it's a static part, match directly
       
   268                 if ($name === null && $part != $pathPart) {
       
   269                     return false;
       
   270                 }
       
   271 
       
   272                 // If it's a variable with requirement, match a regex. If not - everything matches
       
   273                 if ($part !== null && !preg_match($this->_regexDelimiter . '^' . $part . '$' . $this->_regexDelimiter . 'iu', $pathPart)) {
       
   274                     return false;
       
   275                 }
       
   276 
       
   277                 // If it's a variable store it's value for later
       
   278                 if ($name !== null) {
       
   279                     $values[$name] = $pathPart;
       
   280                 } else {
       
   281                     $pathStaticCount++;
       
   282                 }
       
   283             }
       
   284         }
       
   285 
       
   286         // Check if all static mappings have been matched
       
   287         if ($this->_staticCount != $pathStaticCount) {
       
   288             return false;
       
   289         }
       
   290 
       
   291         $return = $values + $this->_wildcardData + $this->_defaults;
       
   292 
       
   293         // Check if all map variables have been initialized
       
   294         foreach ($this->_variables as $var) {
       
   295             if (!array_key_exists($var, $return)) {
       
   296                 return false;
       
   297             }
       
   298         }
       
   299 
       
   300         $this->setMatchedPath(rtrim($matchedPath, $this->_urlDelimiter));
       
   301 
       
   302         $this->_values = $values;
       
   303 
       
   304         return $return;
       
   305 
       
   306     }
       
   307 
       
   308     /**
       
   309      * Assembles user submitted parameters forming a URL path defined by this route
       
   310      *
       
   311      * @param  array $data An array of variable and value pairs used as parameters
       
   312      * @param  boolean $reset Whether or not to set route defaults with those provided in $data
       
   313      * @return string Route path with user submitted parameters
       
   314      */
       
   315     public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
       
   316     {
       
   317         if ($this->_isTranslated) {
       
   318             $translator = $this->getTranslator();
       
   319 
       
   320             if (isset($data['@locale'])) {
       
   321                 $locale = $data['@locale'];
       
   322                 unset($data['@locale']);
       
   323             } else {
       
   324                 $locale = $this->getLocale();
       
   325             }
       
   326         }
       
   327 
       
   328         $url  = array();
       
   329         $flag = false;
       
   330 
       
   331         foreach ($this->_parts as $key => $part) {
       
   332             $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null;
       
   333 
       
   334             $useDefault = false;
       
   335             if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) {
       
   336                 $useDefault = true;
       
   337             }
       
   338 
       
   339             if (isset($name)) {
       
   340                 if (isset($data[$name]) && !$useDefault) {
       
   341                     $value = $data[$name];
       
   342                     unset($data[$name]);
       
   343                 } elseif (!$reset && !$useDefault && isset($this->_values[$name])) {
       
   344                     $value = $this->_values[$name];
       
   345                 } elseif (!$reset && !$useDefault && isset($this->_wildcardData[$name])) {
       
   346                     $value = $this->_wildcardData[$name];
       
   347                 } elseif (isset($this->_defaults[$name])) {
       
   348                     $value = $this->_defaults[$name];
       
   349                 } else {
       
   350                     require_once 'Zend/Controller/Router/Exception.php';
       
   351                     throw new Zend_Controller_Router_Exception($name . ' is not specified');
       
   352                 }
       
   353 
       
   354                 if ($this->_isTranslated && in_array($name, $this->_translatable)) {
       
   355                     $url[$key] = $translator->translate($value, $locale);
       
   356                 } else {
       
   357                     $url[$key] = $value;
       
   358                 }
       
   359             } elseif ($part != '*') {
       
   360                 if ($this->_isTranslated && substr($part, 0, 1) === '@') {
       
   361                     if (substr($part, 1, 1) !== '@') {
       
   362                         $url[$key] = $translator->translate(substr($part, 1), $locale);
       
   363                     } else {
       
   364                         $url[$key] = substr($part, 1);
       
   365                     }
       
   366                 } else {
       
   367                     if (substr($part, 0, 2) === '@@') {
       
   368                         $part = substr($part, 1);
       
   369                     }
       
   370 
       
   371                     $url[$key] = $part;
       
   372                 }
       
   373             } else {
       
   374                 if (!$reset) $data += $this->_wildcardData;
       
   375                 $defaults = $this->getDefaults();
       
   376                 foreach ($data as $var => $value) {
       
   377                     if ($value !== null && (!isset($defaults[$var]) || $value != $defaults[$var])) {
       
   378                         $url[$key++] = $var;
       
   379                         $url[$key++] = $value;
       
   380                         $flag = true;
       
   381                     }
       
   382                 }
       
   383             }
       
   384         }
       
   385 
       
   386         $return = '';
       
   387 
       
   388         foreach (array_reverse($url, true) as $key => $value) {
       
   389             $defaultValue = null;
       
   390 
       
   391             if (isset($this->_variables[$key])) {
       
   392                 $defaultValue = $this->getDefault($this->_variables[$key]);
       
   393 
       
   394                 if ($this->_isTranslated && $defaultValue !== null && isset($this->_translatable[$this->_variables[$key]])) {
       
   395                     $defaultValue = $translator->translate($defaultValue, $locale);
       
   396                 }
       
   397             }
       
   398 
       
   399             if ($flag || $value !== $defaultValue || $partial) {
       
   400                 if ($encode) $value = urlencode($value);
       
   401                 $return = $this->_urlDelimiter . $value . $return;
       
   402                 $flag = true;
       
   403             }
       
   404         }
       
   405 
       
   406         return trim($return, $this->_urlDelimiter);
       
   407 
       
   408     }
       
   409 
       
   410     /**
       
   411      * Return a single parameter of route's defaults
       
   412      *
       
   413      * @param string $name Array key of the parameter
       
   414      * @return string Previously set default
       
   415      */
       
   416     public function getDefault($name) {
       
   417         if (isset($this->_defaults[$name])) {
       
   418             return $this->_defaults[$name];
       
   419         }
       
   420         return null;
       
   421     }
       
   422 
       
   423     /**
       
   424      * Return an array of defaults
       
   425      *
       
   426      * @return array Route defaults
       
   427      */
       
   428     public function getDefaults() {
       
   429         return $this->_defaults;
       
   430     }
       
   431 
       
   432     /**
       
   433      * Get all variables which are used by the route
       
   434      *
       
   435      * @return array
       
   436      */
       
   437     public function getVariables()
       
   438     {
       
   439         return $this->_variables;
       
   440     }
       
   441 
       
   442     /**
       
   443      * Set a default translator
       
   444      *
       
   445      * @param  Zend_Translate $translator
       
   446      * @return void
       
   447      */
       
   448     public static function setDefaultTranslator(Zend_Translate $translator = null)
       
   449     {
       
   450         self::$_defaultTranslator = $translator;
       
   451     }
       
   452 
       
   453     /**
       
   454      * Get the default translator
       
   455      *
       
   456      * @return Zend_Translate
       
   457      */
       
   458     public static function getDefaultTranslator()
       
   459     {
       
   460         return self::$_defaultTranslator;
       
   461     }
       
   462 
       
   463     /**
       
   464      * Set a translator
       
   465      *
       
   466      * @param  Zend_Translate $translator
       
   467      * @return void
       
   468      */
       
   469     public function setTranslator(Zend_Translate $translator)
       
   470     {
       
   471         $this->_translator = $translator;
       
   472     }
       
   473 
       
   474     /**
       
   475      * Get the translator
       
   476      *
       
   477      * @throws Zend_Controller_Router_Exception When no translator can be found
       
   478      * @return Zend_Translate
       
   479      */
       
   480     public function getTranslator()
       
   481     {
       
   482         if ($this->_translator !== null) {
       
   483             return $this->_translator;
       
   484         } else if (($translator = self::getDefaultTranslator()) !== null) {
       
   485             return $translator;
       
   486         } else {
       
   487             try {
       
   488                 $translator = Zend_Registry::get('Zend_Translate');
       
   489             } catch (Zend_Exception $e) {
       
   490                 $translator = null;
       
   491             }
       
   492 
       
   493             if ($translator instanceof Zend_Translate) {
       
   494                 return $translator;
       
   495             }
       
   496         }
       
   497 
       
   498         require_once 'Zend/Controller/Router/Exception.php';
       
   499         throw new Zend_Controller_Router_Exception('Could not find a translator');
       
   500     }
       
   501 
       
   502     /**
       
   503      * Set a default locale
       
   504      *
       
   505      * @param  mixed $locale
       
   506      * @return void
       
   507      */
       
   508     public static function setDefaultLocale($locale = null)
       
   509     {
       
   510         self::$_defaultLocale = $locale;
       
   511     }
       
   512 
       
   513     /**
       
   514      * Get the default locale
       
   515      *
       
   516      * @return mixed
       
   517      */
       
   518     public static function getDefaultLocale()
       
   519     {
       
   520         return self::$_defaultLocale;
       
   521     }
       
   522 
       
   523     /**
       
   524      * Set a locale
       
   525      *
       
   526      * @param  mixed $locale
       
   527      * @return void
       
   528      */
       
   529     public function setLocale($locale)
       
   530     {
       
   531         $this->_locale = $locale;
       
   532     }
       
   533 
       
   534     /**
       
   535      * Get the locale
       
   536      *
       
   537      * @return mixed
       
   538      */
       
   539     public function getLocale()
       
   540     {
       
   541         if ($this->_locale !== null) {
       
   542             return $this->_locale;
       
   543         } else if (($locale = self::getDefaultLocale()) !== null) {
       
   544             return $locale;
       
   545         } else {
       
   546             try {
       
   547                 $locale = Zend_Registry::get('Zend_Locale');
       
   548             } catch (Zend_Exception $e) {
       
   549                 $locale = null;
       
   550             }
       
   551 
       
   552             if ($locale !== null) {
       
   553                 return $locale;
       
   554             }
       
   555         }
       
   556 
       
   557         return null;
       
   558     }
       
   559 }