File indexing completed on 2024-12-22 05:36:34

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Controller
0017  * @subpackage Router
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @version    $Id$
0020  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0021  */
0022 
0023 /** Zend_Controller_Router_Abstract */
0024 // require_once 'Zend/Controller/Router/Abstract.php';
0025 
0026 /** Zend_Controller_Router_Route */
0027 // require_once 'Zend/Controller/Router/Route.php';
0028 
0029 /**
0030  * Ruby routing based Router.
0031  *
0032  * @package    Zend_Controller
0033  * @subpackage Router
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  * @see        http://manuals.rubyonrails.com/read/chapter/65
0037  */
0038 class Zend_Controller_Router_Rewrite extends Zend_Controller_Router_Abstract
0039 {
0040 
0041     /**
0042      * Whether or not to use default routes
0043      *
0044      * @var boolean
0045      */
0046     protected $_useDefaultRoutes = true;
0047 
0048     /**
0049      * Array of routes to match against
0050      *
0051      * @var array
0052      */
0053     protected $_routes = array();
0054 
0055     /**
0056      * Currently matched route
0057      *
0058      * @var string
0059      */
0060     protected $_currentRoute = null;
0061 
0062     /**
0063      * Global parameters given to all routes
0064      *
0065      * @var array
0066      */
0067     protected $_globalParams = array();
0068 
0069     /**
0070      * Separator to use with chain names
0071      *
0072      * @var string
0073      */
0074     protected $_chainNameSeparator = '-';
0075 
0076     /**
0077      * Determines if request parameters should be used as global parameters
0078      * inside this router.
0079      *
0080      * @var boolean
0081      */
0082     protected $_useCurrentParamsAsGlobal = false;
0083 
0084     /**
0085      * Add default routes which are used to mimic basic router behaviour
0086      *
0087      * @return Zend_Controller_Router_Rewrite
0088      */
0089     public function addDefaultRoutes()
0090     {
0091         if (!$this->hasRoute('default')) {
0092             $dispatcher = $this->getFrontController()->getDispatcher();
0093             $request    = $this->getFrontController()->getRequest();
0094 
0095             // require_once 'Zend/Controller/Router/Route/Module.php';
0096             $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);
0097 
0098             $this->_routes = array('default' => $compat) + $this->_routes;
0099         }
0100 
0101         return $this;
0102     }
0103 
0104     /**
0105      * Add route to the route chain
0106      *
0107      * If route contains method setRequest(), it is initialized with a request object
0108      *
0109      * @param  string                                 $name  Name of the route
0110      * @param  Zend_Controller_Router_Route_Interface $route Instance of the route
0111      * @return Zend_Controller_Router_Rewrite
0112      */
0113     public function addRoute($name, Zend_Controller_Router_Route_Interface $route)
0114     {
0115         if (method_exists($route, 'setRequest')) {
0116             $route->setRequest($this->getFrontController()->getRequest());
0117         }
0118 
0119         $this->_routes[$name] = $route;
0120 
0121         return $this;
0122     }
0123 
0124     /**
0125      * Add routes to the route chain
0126      *
0127      * @param  array $routes Array of routes with names as keys and routes as values
0128      * @return Zend_Controller_Router_Rewrite
0129      */
0130     public function addRoutes($routes)
0131     {
0132         foreach ($routes as $name => $route) {
0133             $this->addRoute($name, $route);
0134         }
0135 
0136         return $this;
0137     }
0138 
0139     /**
0140      * Create routes out of Zend_Config configuration
0141      *
0142      * Example INI:
0143      * routes.archive.route = "archive/:year/*"
0144      * routes.archive.defaults.controller = archive
0145      * routes.archive.defaults.action = show
0146      * routes.archive.defaults.year = 2000
0147      * routes.archive.reqs.year = "\d+"
0148      *
0149      * routes.news.type = "Zend_Controller_Router_Route_Static"
0150      * routes.news.route = "news"
0151      * routes.news.defaults.controller = "news"
0152      * routes.news.defaults.action = "list"
0153      *
0154      * And finally after you have created a Zend_Config with above ini:
0155      * $router = new Zend_Controller_Router_Rewrite();
0156      * $router->addConfig($config, 'routes');
0157      *
0158      * @param  Zend_Config $config  Configuration object
0159      * @param  string      $section Name of the config section containing route's definitions
0160      * @throws Zend_Controller_Router_Exception
0161      * @return Zend_Controller_Router_Rewrite
0162      */
0163     public function addConfig(Zend_Config $config, $section = null)
0164     {
0165         if ($section !== null) {
0166             if ($config->{$section} === null) {
0167                 // require_once 'Zend/Controller/Router/Exception.php';
0168                 throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'");
0169             }
0170 
0171             $config = $config->{$section};
0172         }
0173 
0174         foreach ($config as $name => $info) {
0175             $route = $this->_getRouteFromConfig($info);
0176 
0177             if ($route instanceof Zend_Controller_Router_Route_Chain) {
0178                 if (!isset($info->chain)) {
0179                     // require_once 'Zend/Controller/Router/Exception.php';
0180                     throw new Zend_Controller_Router_Exception("No chain defined");
0181                 }
0182 
0183                 if ($info->chain instanceof Zend_Config) {
0184                     $childRouteNames = $info->chain;
0185                 } else {
0186                     $childRouteNames = explode(',', $info->chain);
0187                 }
0188 
0189                 foreach ($childRouteNames as $childRouteName) {
0190                     $childRoute = $this->getRoute(trim($childRouteName));
0191                     $route->chain($childRoute);
0192                 }
0193 
0194                 $this->addRoute($name, $route);
0195             } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) {
0196                 $this->_addChainRoutesFromConfig($name, $route, $info->chains);
0197             } else {
0198                 $this->addRoute($name, $route);
0199             }
0200         }
0201 
0202         return $this;
0203     }
0204 
0205     /**
0206      * Get a route frm a config instance
0207      *
0208      * @param  Zend_Config $info
0209      * @return Zend_Controller_Router_Route_Interface
0210      */
0211     protected function _getRouteFromConfig(Zend_Config $info)
0212     {
0213         $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route';
0214         if (!class_exists($class)) {
0215             // require_once 'Zend/Loader.php';
0216             Zend_Loader::loadClass($class);
0217         }
0218 
0219         $route = call_user_func(
0220             array(
0221                 $class,
0222                 'getInstance'
0223             ), $info
0224         );
0225 
0226         if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) {
0227             $route->isAbstract(true);
0228         }
0229 
0230         return $route;
0231     }
0232 
0233     /**
0234      * Add chain routes from a config route
0235      *
0236      * @param  string                                 $name
0237      * @param  Zend_Controller_Router_Route_Interface $route
0238      * @param  Zend_Config                            $childRoutesInfo
0239      * @return void
0240      */
0241     protected function _addChainRoutesFromConfig(
0242         $name,
0243         Zend_Controller_Router_Route_Interface $route,
0244         Zend_Config $childRoutesInfo
0245     )
0246     {
0247         foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) {
0248             if (is_string($childRouteInfo)) {
0249                 $childRouteName = $childRouteInfo;
0250                 $childRoute     = $this->getRoute($childRouteName);
0251             } else {
0252                 $childRoute = $this->_getRouteFromConfig($childRouteInfo);
0253             }
0254 
0255             if ($route instanceof Zend_Controller_Router_Route_Chain) {
0256                 $chainRoute = clone $route;
0257                 $chainRoute->chain($childRoute);
0258             } else {
0259                 $chainRoute = $route->chain($childRoute);
0260             }
0261 
0262             $chainName = $name . $this->_chainNameSeparator . $childRouteName;
0263 
0264             if (isset($childRouteInfo->chains)) {
0265                 $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains);
0266             } else {
0267                 $this->addRoute($chainName, $chainRoute);
0268             }
0269         }
0270     }
0271 
0272     /**
0273      * Remove a route from the route chain
0274      *
0275      * @param  string $name Name of the route
0276      * @throws Zend_Controller_Router_Exception
0277      * @return Zend_Controller_Router_Rewrite
0278      */
0279     public function removeRoute($name)
0280     {
0281         if (!isset($this->_routes[$name])) {
0282             // require_once 'Zend/Controller/Router/Exception.php';
0283             throw new Zend_Controller_Router_Exception("Route $name is not defined");
0284         }
0285 
0286         unset($this->_routes[$name]);
0287 
0288         return $this;
0289     }
0290 
0291     /**
0292      * Remove all standard default routes
0293      *
0294      * @return Zend_Controller_Router_Rewrite
0295      */
0296     public function removeDefaultRoutes()
0297     {
0298         $this->_useDefaultRoutes = false;
0299 
0300         return $this;
0301     }
0302 
0303     /**
0304      * Check if named route exists
0305      *
0306      * @param  string $name Name of the route
0307      * @return boolean
0308      */
0309     public function hasRoute($name)
0310     {
0311         return isset($this->_routes[$name]);
0312     }
0313 
0314     /**
0315      * Retrieve a named route
0316      *
0317      * @param string $name Name of the route
0318      * @throws Zend_Controller_Router_Exception
0319      * @return Zend_Controller_Router_Route_Interface Route object
0320      */
0321     public function getRoute($name)
0322     {
0323         if (!isset($this->_routes[$name])) {
0324             // require_once 'Zend/Controller/Router/Exception.php';
0325             throw new Zend_Controller_Router_Exception("Route $name is not defined");
0326         }
0327 
0328         return $this->_routes[$name];
0329     }
0330 
0331     /**
0332      * Retrieve a currently matched route
0333      *
0334      * @throws Zend_Controller_Router_Exception
0335      * @return Zend_Controller_Router_Route_Interface Route object
0336      */
0337     public function getCurrentRoute()
0338     {
0339         if (!isset($this->_currentRoute)) {
0340             // require_once 'Zend/Controller/Router/Exception.php';
0341             throw new Zend_Controller_Router_Exception("Current route is not defined");
0342         }
0343 
0344         return $this->getRoute($this->_currentRoute);
0345     }
0346 
0347     /**
0348      * Retrieve a name of currently matched route
0349      *
0350      * @throws Zend_Controller_Router_Exception
0351      * @return string Route name
0352      */
0353     public function getCurrentRouteName()
0354     {
0355         if (!isset($this->_currentRoute)) {
0356             // require_once 'Zend/Controller/Router/Exception.php';
0357             throw new Zend_Controller_Router_Exception("Current route is not defined");
0358         }
0359 
0360         return $this->_currentRoute;
0361     }
0362 
0363     /**
0364      * Retrieve an array of routes added to the route chain
0365      *
0366      * @return array All of the defined routes
0367      */
0368     public function getRoutes()
0369     {
0370         return $this->_routes;
0371     }
0372 
0373     /**
0374      * Find a matching route to the current PATH_INFO and inject
0375      * returning values to the Request object.
0376      *
0377      * @param Zend_Controller_Request_Abstract $request
0378      * @throws Zend_Controller_Router_Exception
0379      * @return Zend_Controller_Request_Abstract Request object
0380      */
0381     public function route(Zend_Controller_Request_Abstract $request)
0382     {
0383         if (!$request instanceof Zend_Controller_Request_Http) {
0384             // require_once 'Zend/Controller/Router/Exception.php';
0385             throw new Zend_Controller_Router_Exception(
0386                 'Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'
0387             );
0388         }
0389 
0390         if ($this->_useDefaultRoutes) {
0391             $this->addDefaultRoutes();
0392         }
0393 
0394         // Find the matching route
0395         $routeMatched = false;
0396 
0397         foreach (array_reverse($this->_routes, true) as $name => $route) {
0398             // TODO: Should be an interface method. Hack for 1.0 BC
0399             if (method_exists($route, 'isAbstract') && $route->isAbstract()) {
0400                 continue;
0401             }
0402 
0403             // TODO: Should be an interface method. Hack for 1.0 BC
0404             if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
0405                 $match = $request->getPathInfo();
0406             } else {
0407                 $match = $request;
0408             }
0409 
0410             if ($params = $route->match($match)) {
0411                 $this->_setRequestParams($request, $params);
0412                 $this->_currentRoute = $name;
0413                 $routeMatched        = true;
0414                 break;
0415             }
0416         }
0417 
0418         if (!$routeMatched) {
0419             // require_once 'Zend/Controller/Router/Exception.php';
0420             throw new Zend_Controller_Router_Exception('No route matched the request', 404);
0421         }
0422 
0423         if ($this->_useCurrentParamsAsGlobal) {
0424             $params = $request->getParams();
0425             foreach ($params as $param => $value) {
0426                 $this->setGlobalParam($param, $value);
0427             }
0428         }
0429 
0430         return $request;
0431     }
0432 
0433     /**
0434      * Sets parameters for request object
0435      *
0436      * Module name, controller name and action name
0437      *
0438      * @param Zend_Controller_Request_Abstract $request
0439      * @param array                            $params
0440      */
0441     protected function _setRequestParams($request, $params)
0442     {
0443         foreach ($params as $param => $value) {
0444 
0445             $request->setParam($param, $value);
0446 
0447             if ($param === $request->getModuleKey()) {
0448                 $request->setModuleName($value);
0449             }
0450             if ($param === $request->getControllerKey()) {
0451                 $request->setControllerName($value);
0452             }
0453             if ($param === $request->getActionKey()) {
0454                 $request->setActionName($value);
0455             }
0456         }
0457     }
0458 
0459     /**
0460      * Generates a URL path that can be used in URL creation, redirection, etc.
0461      *
0462      * @param  array $userParams Options passed by a user used to override parameters
0463      * @param  mixed $name       The name of a Route to use
0464      * @param  bool  $reset      Whether to reset to the route defaults ignoring URL params
0465      * @param  bool  $encode     Tells to encode URL parts on output
0466      * @throws Zend_Controller_Router_Exception
0467      * @return string Resulting absolute URL path
0468      */
0469     public function assemble($userParams, $name = null, $reset = false, $encode = true)
0470     {
0471         if (!is_array($userParams)) {
0472             // require_once 'Zend/Controller/Router/Exception.php';
0473             throw new Zend_Controller_Router_Exception('userParams must be an array');
0474         }
0475 
0476         if ($name == null) {
0477             try {
0478                 $name = $this->getCurrentRouteName();
0479             } catch (Zend_Controller_Router_Exception $e) {
0480                 $name = 'default';
0481             }
0482         }
0483 
0484         // Use UNION (+) in order to preserve numeric keys
0485         $params = $userParams + $this->_globalParams;
0486 
0487         $route = $this->getRoute($name);
0488         $url   = $route->assemble($params, $reset, $encode);
0489 
0490         if (!preg_match('|^[a-z]+://|', $url)) {
0491             $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url;
0492         }
0493 
0494         return $url;
0495     }
0496 
0497     /**
0498      * Set a global parameter
0499      *
0500      * @param  string $name
0501      * @param  mixed  $value
0502      * @return Zend_Controller_Router_Rewrite
0503      */
0504     public function setGlobalParam($name, $value)
0505     {
0506         $this->_globalParams[$name] = $value;
0507 
0508         return $this;
0509     }
0510 
0511     /**
0512      * Set the separator to use with chain names
0513      *
0514      * @param string $separator The separator to use
0515      * @return Zend_Controller_Router_Rewrite
0516      */
0517     public function setChainNameSeparator($separator)
0518     {
0519         $this->_chainNameSeparator = $separator;
0520 
0521         return $this;
0522     }
0523 
0524     /**
0525      * Get the separator to use for chain names
0526      *
0527      * @return string
0528      */
0529     public function getChainNameSeparator()
0530     {
0531         return $this->_chainNameSeparator;
0532     }
0533 
0534     /**
0535      * Determines/returns whether to use the request parameters as global parameters.
0536      *
0537      * @param boolean|null $use
0538      *              Null/unset when you want to retrieve the current state.
0539      *              True when request parameters should be global, false otherwise
0540      * @return boolean|Zend_Controller_Router_Rewrite
0541      *              Returns a boolean if first param isn't set, returns an
0542      *              instance of Zend_Controller_Router_Rewrite otherwise.
0543      *
0544      */
0545     public function useRequestParametersAsGlobal($use = null)
0546     {
0547         if ($use === null) {
0548             return $this->_useCurrentParamsAsGlobal;
0549         }
0550 
0551         $this->_useCurrentParamsAsGlobal = (bool)$use;
0552 
0553         return $this;
0554     }
0555 }