router.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: router_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * Parses the request URL into controller, action, and parameters.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00011  * Copyright 2005-2008, Cake Software Foundation, Inc.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.libs
00023  * @since           CakePHP(tm) v 0.2.9
00024  * @version         $Revision: 580 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 /**
00030  * Included libraries.
00031  *
00032  */
00033 if (!class_exists('Object')) {
00034     App::import('Core', 'Object');
00035 }
00036 
00037 /**
00038  * Parses the request URL into controller, action, and parameters.
00039  *
00040  * @package     cake
00041  * @subpackage  cake.cake.libs
00042  */
00043 class Router extends Object {
00044 /**
00045  * Array of routes
00046  *
00047  * @var array
00048  * @access public
00049  */
00050     var $routes = array();
00051 /**
00052  * CAKE_ADMIN route
00053  *
00054  * @var array
00055  * @access private
00056  */
00057     var $__admin = null;
00058 /**
00059  * List of action prefixes used in connected routes
00060  *
00061  * @var array
00062  * @access private
00063  */
00064     var $__prefixes = array();
00065 /**
00066  * Directive for Router to parse out file extensions for mapping to Content-types.
00067  *
00068  * @var boolean
00069  * @access private
00070  */
00071     var $__parseExtensions = false;
00072 /**
00073  * List of valid extensions to parse from a URL.  If null, any extension is allowed.
00074  *
00075  * @var array
00076  * @access private
00077  */
00078     var $__validExtensions = null;
00079 /**
00080  * 'Constant' regular expression definitions for named route elements
00081  *
00082  * @var array
00083  * @access private
00084  */
00085     var $__named = array(
00086         'Action'    => 'index|show|add|create|edit|update|remove|del|delete|view|item',
00087         'Year'      => '[12][0-9]{3}',
00088         'Month'     => '0[1-9]|1[012]',
00089         'Day'       => '0[1-9]|[12][0-9]|3[01]',
00090         'ID'        => '[0-9]+',
00091         'UUID'      => '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}'
00092     );
00093 /**
00094  * Stores all information necessary to decide what named arguments are parsed under what conditions.
00095  *
00096  * @var string
00097  * @access public
00098  */
00099     var $named = array(
00100         'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'),
00101         'greedy' => true,
00102         'separator' => ':',
00103         'rules' => false,
00104     );
00105 /**
00106  * The route matching the URL of the current request
00107  *
00108  * @var array
00109  * @access private
00110  */
00111     var $__currentRoute = array();
00112 /**
00113  * HTTP header shortcut map.  Used for evaluating header-based route expressions.
00114  *
00115  * @var array
00116  * @access private
00117  */
00118     var $__headerMap = array(
00119         'type'      => 'content_type',
00120         'method'    => 'request_method',
00121         'server'    => 'server_name'
00122     );
00123 /**
00124  * Default HTTP request method => controller action map.
00125  *
00126  * @var array
00127  * @access private
00128  */
00129     var $__resourceMap = array(
00130         array('action' => 'index',  'method' => 'GET',      'id' => false),
00131         array('action' => 'view',   'method' => 'GET',      'id' => true),
00132         array('action' => 'add',    'method' => 'POST',     'id' => false),
00133         array('action' => 'edit',   'method' => 'PUT',      'id' => true),
00134         array('action' => 'delete', 'method' => 'DELETE',   'id' => true),
00135         array('action' => 'edit',   'method' => 'POST',     'id' => true)
00136     );
00137 /**
00138  * List of resource-mapped controllers
00139  *
00140  * @var array
00141  * @access private
00142  */
00143     var $__resourceMapped = array();
00144 /**
00145  * Maintains the parameter stack for the current request
00146  *
00147  * @var array
00148  * @access private
00149  */
00150     var $__params = array();
00151 /**
00152  * Maintains the path stack for the current request
00153  *
00154  * @var array
00155  * @access private
00156  */
00157     var $__paths = array();
00158 /**
00159  * Keeps Router state to determine if default routes have already been connected
00160  *
00161  * @var boolean
00162  * @access private
00163  */
00164     var $__defaultsMapped = false;
00165 /**
00166  * Gets a reference to the Router object instance
00167  *
00168  * @return object Object instance
00169  * @access public
00170  * @static
00171  */
00172     function &getInstance() {
00173         static $instance = array();
00174 
00175         if (!isset($instance[0]) || !$instance[0]) {
00176             $instance[0] =& new Router();
00177         }
00178         return $instance[0];
00179     }
00180 /**
00181  * Gets the named route elements for use in app/config/routes.php
00182  *
00183  * @return array Named route elements
00184  * @access public
00185  * @see Router::$__named
00186  * @static
00187  */
00188     function getNamedExpressions() {
00189         $_this =& Router::getInstance();
00190         return $_this->__named;
00191     }
00192 /**
00193  * Returns this object's routes array. Returns false if there are no routes available.
00194  *
00195  * @param string $route         An empty string, or a route string "/"
00196  * @param array $default        NULL or an array describing the default route
00197  * @param array $params         An array matching the named elements in the route to regular expressions which that element should match.
00198  * @see routes
00199  * @return array            Array of routes
00200  * @access public
00201  * @static
00202  */
00203     function connect($route, $default = array(), $params = array()) {
00204         $_this =& Router::getInstance();
00205         $admin = Configure::read('Routing.admin');
00206         $default = array_merge(array('action' => 'index'), $default);
00207 
00208         if(isset($default[$admin])) {
00209             $default['prefix'] = $admin;
00210         }
00211 
00212         if (isset($default['prefix'])) {
00213             $_this->__prefixes[] = $default['prefix'];
00214             $_this->__prefixes = array_keys(array_flip($_this->__prefixes));
00215         }
00216 
00217         if (list($pattern, $names) = $_this->writeRoute($route, $default, $params)) {
00218             $_this->routes[] = array($route, $pattern, $names, array_merge(array('plugin' => null, 'controller' => null), $default), $params);
00219         }
00220         return $_this->routes;
00221     }
00222 /**
00223  *Specifies what named parameters CakePHP should be parsing. The most common setups are:
00224  *
00225  * Do not parse any named parameters:
00226  *  Router::connectNamed(false);
00227  *
00228  * Parse only default parameters used for CakePHP's pagination:
00229  *  Router::connectNamed(false, array('default' => true));
00230  *
00231  * Parse only the page parameter if its value is a number:
00232  *  Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false));
00233  *
00234  * Parse only the page parameter no mater what.
00235  *  Router::connectNamed(array('page'), array('default' => false, 'greedy' => false));
00236  *
00237  * Parse only the page parameter if the current action is 'index'.
00238  *  Router::connectNamed(array('page' => array('action' => 'index')), array('default' => false, 'greedy' => false));
00239  *
00240  * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
00241  *  Router::connectNamed(array('page' => array('action' => 'index', 'controller' => 'pages')), array('default' => false, 'greedy' => false));
00242  *
00243  * @param array $named A list of named parameters. Key value pairs are accepted where values are either regex strings to match, or arrays as seen above.
00244  * @param array $options Allows to control all settings: separator, greedy, reset, default
00245  * @access public
00246  * @static
00247  */
00248     function connectNamed($named, $options = array()) {
00249         $_this =& Router::getInstance();
00250 
00251         if (isset($options['argSeparator'])) {
00252             $_this->named['separator'] = $options['argSeparator'];
00253             unset($options['argSeparator']);
00254         }
00255 
00256         if ($named === true || $named === false) {
00257             $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options);
00258             $named = array();
00259         }
00260         $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options);
00261 
00262         if ($options['reset'] == true || $_this->named['rules'] === false) {
00263             $_this->named['rules'] = array();
00264         }
00265 
00266         if ($options['default']) {
00267             $named = array_merge($named, $_this->named['default']);
00268         }
00269 
00270         foreach ($named as $key => $val) {
00271             if (is_numeric($key)) {
00272                 $_this->named['rules'][$val] = true;
00273             } else {
00274                 $_this->named['rules'][$key] = $val;
00275             }
00276         }
00277         $_this->named['greedy'] = $options['greedy'];
00278         return $_this->named;
00279     }
00280 /**
00281  * Creates REST resource routes for the given controller(s)
00282  *
00283  * @param mixed $controller     A controller name or array of controller names (i.e. "Posts" or "ListItems")
00284  * @param array $options        Options to use when generating REST routes
00285  *                  'id' -      The regular expression fragment to use when matching IDs.  By default, matches
00286  *                              integer values and UUIDs.
00287  *                  'prefix' -  URL prefix to use for the generated routes.  Defaults to '/'.
00288  * @access public
00289  * @static
00290  */
00291     function mapResources($controller, $options = array()) {
00292         $_this =& Router::getInstance();
00293         $options = array_merge(array('prefix' => '/', 'id' => $_this->__named['ID'] . '|' . $_this->__named['UUID']), $options);
00294         $prefix = $options['prefix'];
00295 
00296         foreach ((array)$controller as $ctlName) {
00297             $urlName = Inflector::underscore($ctlName);
00298 
00299             foreach ($_this->__resourceMap as $params) {
00300                 extract($params);
00301                 $id = ife($id, '/:id', '');
00302 
00303                 Router::connect(
00304                     "{$prefix}{$urlName}{$id}",
00305                     array('controller' => $urlName, 'action' => $action, '[method]' => $params['method']),
00306                     array('id' => $options['id'], 'pass' => array('id'))
00307                 );
00308             }
00309             $_this->__resourceMapped[] = $urlName;
00310         }
00311     }
00312 /**
00313  * Builds a route regular expression
00314  *
00315  * @param string $route         An empty string, or a route string "/"
00316  * @param array $default        NULL or an array describing the default route
00317  * @param array $params         An array matching the named elements in the route to regular expressions which that element should match.
00318  * @return string
00319  * @see routes
00320  * @access public
00321  * @static
00322  */
00323     function writeRoute($route, $default, $params) {
00324         if (empty($route) || ($route == '/')) {
00325             return array('/^[\/]*$/', array());
00326         }
00327         $names = array();
00328         $elements = explode('/', $route);
00329 
00330         foreach ($elements as $element) {
00331             if (empty($element)) {
00332                 continue;
00333             }
00334             $q = null;
00335             $element = trim($element);
00336             $namedParam = strpos($element, ':') !== false;
00337 
00338             if ($namedParam && preg_match('/^:([^:]+)$/', $element, $r)) {
00339                 if (isset($params[$r[1]])) {
00340                     if ($r[1] != 'plugin' && array_key_exists($r[1], $default)) {
00341                         $q = '?';
00342                     }
00343                     $parsed[] = '(?:/(' . $params[$r[1]] . ')' . $q . ')' . $q;
00344                 } else {
00345                     $parsed[] = '(?:/([^\/]+))?';
00346                 }
00347                 $names[] = $r[1];
00348             } elseif ($element == '*') {
00349                 $parsed[] = '(?:/(.*))?';
00350             } else if ($namedParam && preg_match_all('/(?!\\\\):([a-z_0-9]+)/i', $element, $matches)) {
00351                 $matchCount = count($matches[1]);
00352 
00353                 foreach ($matches[1] as $i => $name) {
00354                     $pos = strpos($element, ':' . $name);
00355                     $before = substr($element, 0, $pos);
00356                     $element = substr($element, $pos+strlen($name)+1);
00357                     $after = null;
00358 
00359                     if ($i + 1 == $matchCount && $element) {
00360                         $after = preg_quote($element);
00361                     }
00362 
00363                     if ($i == 0) {
00364                         $before = '/' . $before;
00365                     }
00366                     $before = preg_quote($before, '#');
00367 
00368                     if (isset($params[$name])) {
00369                         if (isset($default[$name]) && $name != 'plugin') {
00370                             $q = '?';
00371                         }
00372                         $parsed[] = '(?:' . $before . '(' . $params[$name] . ')' . $q . $after . ')' . $q;
00373                     } else {
00374                         $parsed[] = '(?:' . $before . '([^\/]+)' . $after . ')?';
00375                     }
00376                     $names[] = $name;
00377                 }
00378             } else {
00379                 $parsed[] = '/' . $element;
00380             }
00381         }
00382         return array('#^' . join('', $parsed) . '[\/]*$#', $names);
00383     }
00384 /**
00385  * Returns the list of prefixes used in connected routes
00386  *
00387  * @return array A list of prefixes used in connected routes
00388  * @access public
00389  * @static
00390  */
00391     function prefixes() {
00392         $_this =& Router::getInstance();
00393         return $_this->__prefixes;
00394     }
00395 /**
00396  * Parses given URL and returns an array of controllers, action and parameters
00397  * taken from that URL.
00398  *
00399  * @param string $url URL to be parsed
00400  * @return array Parsed elements from URL
00401  * @access public
00402  * @static
00403  */
00404     function parse($url) {
00405         $_this =& Router::getInstance();
00406         if (!$_this->__defaultsMapped) {
00407             $_this->__connectDefaultRoutes();
00408         }
00409         $out = array('pass' => array(), 'named' => array());
00410         $r = $ext = null;
00411 
00412         if (ini_get('magic_quotes_gpc') == 1) {
00413             $url = stripslashes_deep($url);
00414         }
00415 
00416         if ($url && strpos($url, '/') !== 0) {
00417             $url = '/' . $url;
00418         }
00419         if (strpos($url, '?') !== false) {
00420             $url = substr($url, 0, strpos($url, '?'));
00421         }
00422         extract($_this->__parseExtension($url));
00423         foreach ($_this->routes as $route) {
00424             if (($r = $_this->matchRoute($route, $url)) !== false) {
00425                 $_this->__currentRoute[] = $route;
00426                 list($route, $regexp, $names, $defaults, $params) = $route;
00427                 $argOptions = array();
00428                 if (array_key_exists('named', $params)) {
00429                     $argOptions['named'] = $params['named'];
00430                     unset($params['named']);
00431                 }
00432                 if (array_key_exists('greedy', $params)) {
00433                     $argOptions['greedy'] = $params['greedy'];
00434                     unset($params['greedy']);
00435                 }
00436                 array_shift($r);
00437 
00438                 foreach ($names as $name) {
00439                     $out[$name] = null;
00440                 }
00441 
00442                 if (is_array($defaults)) {
00443                     foreach ($defaults as $name => $value) {
00444                         if (preg_match('#[a-zA-Z_\-]#i', $name)) {
00445                             $out[$name] = $value;
00446                         } else {
00447                             $out['pass'][] = $value;
00448                         }
00449                     }
00450                 }
00451 
00452                 foreach ($r as $key => $found) {
00453                     if (empty($found)) {
00454                         continue;
00455                     }
00456 
00457                     if (isset($names[$key])) {
00458                         $out[$names[$key]] = $_this->stripEscape($found);
00459                     } elseif (isset($names[$key]) && empty($names[$key]) && empty($out[$names[$key]])) {
00460                         break;
00461                     } else {
00462                         $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']);
00463                         extract($_this->getArgs($found, $argOptions));
00464                         $out['pass'] = array_merge($out['pass'], $pass);
00465                         $out['named'] = $named;
00466                     }
00467                 }
00468 
00469                 if (isset($params['pass'])) {
00470                     for ($i = count($params['pass']) - 1; $i > -1; $i--) {
00471                         if (isset($out[$params['pass'][$i]])) {
00472                             array_unshift($out['pass'], $out[$params['pass'][$i]]);
00473                         }
00474                     }
00475                 }
00476                 break;
00477             }
00478         }
00479 
00480         if (!empty($ext)) {
00481             $out['url']['ext'] = $ext;
00482         }
00483         return $out;
00484     }
00485 /**
00486  * Checks to see if the given URL matches the given route
00487  *
00488  * @param array $route
00489  * @param string $url
00490  * @return mixed Boolean false on failure, otherwise array
00491  * @access public
00492  */
00493     function matchRoute($route, $url) {
00494         $_this =& Router::getInstance();
00495         list($route, $regexp, $names, $defaults) = $route;
00496 
00497         if (!preg_match($regexp, $url, $r)) {
00498             return false;
00499         } else {
00500             foreach ($defaults as $key => $val) {
00501                 if ($key{0} == '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) {
00502                     if (isset($_this->__headerMap[$header[1]])) {
00503                         $header = $_this->__headerMap[$header[1]];
00504                     } else {
00505                         $header = 'http_' . $header[1];
00506                     }
00507 
00508                     if (!is_array($val)) {
00509                         $val = array($val);
00510                     }
00511                     $h = false;
00512                     foreach ($val as $v) {
00513                         if (env(strtoupper($header)) == $v) {
00514                             $h = true;
00515                         }
00516                     }
00517                     if (!$h) {
00518                         return false;
00519                     }
00520                 }
00521             }
00522         }
00523         return $r;
00524     }
00525 /**
00526  * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
00527  *
00528  * @param string $url
00529  * @return array Returns an array containing the altered URL and the parsed extension.
00530  * @access private
00531  */
00532     function __parseExtension($url) {
00533         $ext = null;
00534         $_this =& Router::getInstance();
00535 
00536         if ($_this->__parseExtensions) {
00537             if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) == 1) {
00538                 $match = substr($match[0], 1);
00539                 if (empty($_this->__validExtensions)) {
00540                     $url = substr($url, 0, strpos($url, '.' . $match));
00541                     $ext = $match;
00542                 } else {
00543                     foreach ($_this->__validExtensions as $name) {
00544                         if (strcasecmp($name, $match) === 0) {
00545                             $url = substr($url, 0, strpos($url, '.' . $name));
00546                             $ext = $match;
00547                         }
00548                     }
00549                 }
00550             }
00551             if (empty($ext)) {
00552                 $ext = 'html';
00553             }
00554         }
00555         return compact('ext', 'url');
00556     }
00557 /**
00558  * Connects the default, built-in routes, including admin routes, and (deprecated) web services
00559  * routes.
00560  *
00561  * @access private
00562  */
00563     function __connectDefaultRoutes() {
00564         $_this =& Router::getInstance();
00565         if ($_this->__defaultsMapped) {
00566             return;
00567         }
00568 
00569         if ($admin = Configure::read('Routing.admin')) {
00570             $params = array('prefix' => $admin, $admin => true);
00571         }
00572 
00573         if ($plugins = Configure::listObjects('plugin')) {
00574             $Inflector =& Inflector::getInstance();
00575             $plugins = array_map(array(&$Inflector, 'underscore'), $plugins);
00576         }
00577 
00578         if(!empty($plugins)) {
00579             $match = array('plugin' => implode('|', $plugins));
00580             $_this->connect('/:plugin/:controller/:action/*', array(), $match);
00581 
00582             if ($admin) {
00583                 $_this->connect("/{$admin}/:plugin/:controller", $params, $match);
00584                 $_this->connect("/{$admin}/:plugin/:controller/:action/*", $params, $match);
00585             }
00586         }
00587 
00588         if ($admin) {
00589             $_this->connect("/{$admin}/:controller", $params);
00590             $_this->connect("/{$admin}/:controller/:action/*", $params);
00591         }
00592         $_this->connect('/:controller', array('action' => 'index'));
00593         $_this->connect('/:controller/:action/*');
00594 
00595         if ($_this->named['rules'] === false) {
00596             $_this->connectNamed(true);
00597         }
00598         $_this->__defaultsMapped = true;
00599     }
00600 /**
00601  * Takes parameter and path information back from the Dispatcher
00602  *
00603  * @param array $params Parameters and path information
00604  * @access public
00605  * @static
00606  */
00607     function setRequestInfo($params) {
00608         $_this =& Router::getInstance();
00609         $defaults = array('plugin' => null, 'controller' => null, 'action' => null);
00610         $params[0] = array_merge($defaults, (array)$params[0]);
00611         $params[1] = array_merge($defaults, (array)$params[1]);
00612         list($_this->__params[], $_this->__paths[]) = $params;
00613 
00614         if (count($_this->__paths)) {
00615             if (isset($_this->__paths[0]['namedArgs'])) {
00616                 foreach ($_this->__paths[0]['namedArgs'] as $arg => $value) {
00617                     $_this->named['rules'][$arg] = true;
00618                 }
00619             }
00620         }
00621     }
00622 /**
00623  * Gets parameter information
00624  *
00625  * @param boolean $current Get current parameter (true)
00626  * @return array Parameter information
00627  * @access public
00628  * @static
00629  */
00630     function getParams($current = false) {
00631         $_this =& Router::getInstance();
00632         if ($current) {
00633             return $_this->__params[count($_this->__params) - 1];
00634         }
00635         if (isset($_this->__params[0])) {
00636             return $_this->__params[0];
00637         }
00638         return array();
00639     }
00640 /**
00641  * Gets URL parameter by name
00642  *
00643  * @param string $name Parameter name
00644  * @param boolean $current Current parameter
00645  * @return string Parameter value
00646  * @access public
00647  * @static
00648  */
00649     function getParam($name = 'controller', $current = false) {
00650         $_this =& Router::getInstance();
00651         $params = Router::getParams($current);
00652         if (isset($params[$name])) {
00653             return $params[$name];
00654         }
00655         return null;
00656     }
00657 /**
00658  * Gets path information
00659  *
00660  * @param boolean $current Current parameter
00661  * @return array
00662  * @access public
00663  * @static
00664  */
00665     function getPaths($current = false) {
00666         $_this =& Router::getInstance();
00667         if ($current) {
00668             return $_this->__paths[count($_this->__paths) - 1];
00669         }
00670         if (!isset($_this->__paths[0])) {
00671             return array('base' => null);
00672         }
00673         return $_this->__paths[0];
00674     }
00675 /**
00676  * Reloads default Router settings
00677  *
00678  * @access public
00679  * @static
00680  */
00681     function reload() {
00682         $_this =& Router::getInstance();
00683         foreach (get_class_vars('Router') as $key => $val) {
00684             $_this->{$key} = $val;
00685         }
00686     }
00687 /**
00688  * Promote a route (by default, the last one added) to the beginning of the list
00689  *
00690  * @param $which A zero-based array index representing the route to move. For example,
00691  *               if 3 routes have been added, the last route would be 2.
00692  * @return boolean Retuns false if no route exists at the position specified by $which.
00693  * @access public
00694  * @static
00695  */
00696     function promote($which = null) {
00697         $_this =& Router::getInstance();
00698         if ($which == null) {
00699             $which = count($_this->routes) - 1;
00700         }
00701         if (!isset($_this->routes[$which])) {
00702             return false;
00703         }
00704         $route = $_this->routes[$which];
00705         unset($_this->routes[$which]);
00706         array_unshift($_this->routes, $route);
00707         return true;
00708     }
00709 /**
00710  * Finds URL for specified action.
00711  *
00712  * Returns an URL pointing to a combination of controller and action. Param
00713  * $url can be:
00714  *  + Empty - the method will find adress to actuall controller/action.
00715  *  + '/' - the method will find base URL of application.
00716  *  + A combination of controller/action - the method will find url for it.
00717  *
00718  * @param  mixed  $url    Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
00719  *                        or an array specifying any of the following: 'controller', 'action',
00720  *                        and/or 'plugin', in addition to named arguments (keyed array elements),
00721  *                        and standard URL arguments (indexed array elements)
00722  * @param boolean $full If true, the full base URL will be prepended to the result
00723  * @return string  Full translated URL with base path.
00724  * @access public
00725  * @static
00726  */
00727     function url($url = null, $full = false) {
00728         $_this =& Router::getInstance();
00729         $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
00730         $admin = Configure::read('Routing.admin');
00731 
00732         if (!empty($_this->__params)) {
00733             if (isset($this) && !isset($this->params['requested'])) {
00734                 $params = $_this->__params[0];
00735             } else {
00736                 $params = end($_this->__params);
00737             }
00738         }
00739         $path = array('base' => null);
00740 
00741         if (!empty($_this->__paths)) {
00742             if (isset($this) && !isset($this->params['requested'])) {
00743                 $path = $_this->__paths[0];
00744             } else {
00745                 $path = end($_this->__paths);
00746             }
00747         }
00748         $base = $path['base'];
00749         $extension = $output = $mapped = $q = $frag = null;
00750 
00751         if (is_array($url)) {
00752             if (isset($url['base']) && $url['base'] === false) {
00753                 $base = null;
00754                 unset($url['base']);
00755             }
00756             if (isset($url['full_base']) && $url['full_base'] == true) {
00757                 $full = true;
00758                 unset($url['full_base']);
00759             }
00760             if (isset($url['?'])) {
00761                 $q = $url['?'];
00762                 unset($url['?']);
00763             }
00764             if (isset($url['#'])) {
00765                 $frag = '#' . urlencode($url['#']);
00766                 unset($url['#']);
00767             }
00768             if (empty($url['action'])) {
00769                 if (empty($url['controller']) || $params['controller'] == $url['controller']) {
00770                     $url['action'] = $params['action'];
00771                 } else {
00772                     $url['action'] = 'index';
00773                 }
00774             }
00775             if ($admin) {
00776                 if (!isset($url[$admin]) && !empty($params[$admin])) {
00777                     $url[$admin] = true;
00778                 } elseif ($admin && isset($url[$admin]) && !$url[$admin]) {
00779                     unset($url[$admin]);
00780                 }
00781             }
00782             $plugin = false;
00783 
00784             if (array_key_exists('plugin', $url)) {
00785                 $plugin = $url['plugin'];
00786             }
00787 
00788             $url = array_merge(array('controller' => $params['controller'], 'plugin' => $params['plugin']), Set::filter($url, true));
00789 
00790             if ($plugin !== false) {
00791                 $url['plugin'] = $plugin;
00792             }
00793 
00794             if (isset($url['ext'])) {
00795                 $extension = '.' . $url['ext'];
00796                 unset($url['ext']);
00797             }
00798             $match = false;
00799 
00800             foreach ($_this->routes as $route) {
00801                 $originalUrl = $url;
00802                 if (isset($route[4]['persist'], $_this->__params[0])) {
00803                     $url = am(array_intersect_key($params, Set::combine($route[4]['persist'], '/')), $url);
00804                 }
00805                 if ($match = $_this->mapRouteElements($route, $url)) {
00806                     $output = trim($match, '/');
00807                     $url = array();
00808                     break;
00809                 }
00810                 $url = $originalUrl;
00811             }
00812             $named = $args = array();
00813             $skip = array('bare', 'action', 'controller', 'plugin', 'ext', '?', '#', 'prefix', $admin);
00814 
00815             $keys = array_values(array_diff(array_keys($url), $skip));
00816             $count = count($keys);
00817 
00818             // Remove this once parsed URL parameters can be inserted into 'pass'
00819             for ($i = 0; $i < $count; $i++) {
00820                 if ($i == 0 && is_numeric($keys[$i]) && in_array('id', $keys)) {
00821                     $args[0] = $url[$keys[$i]];
00822                 } elseif (is_numeric($keys[$i]) || $keys[$i] == 'id') {
00823                     $args[] = $url[$keys[$i]];
00824                 } else {
00825                     $named[$keys[$i]] = $url[$keys[$i]];
00826                 }
00827             }
00828 
00829             if ($match === false) {
00830                 list($args, $named)  = array(Set::filter($args, true), Set::filter($named));
00831                 if (!empty($url[$admin])) {
00832                     $url['action'] = str_replace($admin . '_', '', $url['action']);
00833                 }
00834 
00835                 if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] == 'index')) {
00836                     $url['action'] = null;
00837                 }
00838 
00839                 $urlOut = Set::filter(array($url['controller'], $url['action']));
00840 
00841                 if (isset($url['plugin']) && $url['plugin'] != $url['controller']) {
00842                     array_unshift($urlOut, $url['plugin']);
00843                 }
00844 
00845                 if($admin && isset($url[$admin])) {
00846                     array_unshift($urlOut, $admin);
00847                 }
00848                 $output = join('/', $urlOut) . '/';
00849             }
00850 
00851             if (!empty($args)) {
00852                 $args = join('/', $args);
00853                 if ($output{strlen($output) - 1} != '/') {
00854                     $args = '/'. $args;
00855                 }
00856                 $output .= $args;
00857             }
00858 
00859             if (!empty($named)) {
00860                 foreach ($named as $name => $value) {
00861                     $output .= '/' . $name . $_this->named['separator'] . $value;
00862                 }
00863             }
00864 
00865             $output = str_replace('//', '/', $base . '/' . $output);
00866         } else {
00867             if (((strpos($url, '://')) || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (substr($url, 0, 1) == '#')) {
00868                 return $url;
00869             }
00870             if (empty($url)) {
00871                 if (!isset($path['here'])) {
00872                     $path['here'] = '/';
00873                 }
00874                 $output = $path['here'];
00875             } elseif (substr($url, 0, 1) == '/') {
00876                 $output = $base . $url;
00877             } else {
00878                 $output = $base . '/';
00879                 if ($admin && isset($params[$admin])) {
00880                     $output .= $admin . '/';
00881                 }
00882                 if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
00883                     $output .= Inflector::underscore($params['plugin']) . '/';
00884                 }
00885                 $output .= Inflector::underscore($params['controller']) . '/' . $url;
00886             }
00887             $output = str_replace('//', '/', $output);
00888         }
00889         if ($full) {
00890             $output = FULL_BASE_URL . $output;
00891         }
00892         if (!empty($extension) && substr($output, -1) == '/') {
00893             $output = substr($output, 0, -1);
00894         }
00895 
00896         return $output . $extension . $_this->queryString($q) . $frag;
00897     }
00898 /**
00899  * Maps a URL array onto a route and returns the string result, or false if no match
00900  *
00901  * @param array $route Route Route
00902  * @param array $url URL URL to map
00903  * @return mixed Result (as string) or false if no match
00904  * @access public
00905  * @static
00906  */
00907     function mapRouteElements($route, $url) {
00908         $_this =& Router::getInstance();
00909         if (isset($route[3]['prefix'])) {
00910             $prefix = $route[3]['prefix'];
00911             unset($route[3]['prefix']);
00912         }
00913 
00914         $pass = array();
00915         $defaults = $route[3];
00916         $routeParams = $route[2];
00917         $params = Set::diff($url, $defaults);
00918         $urlInv = array_combine(array_values($url), array_keys($url));
00919 
00920         $i = 0;
00921         while (isset($defaults[$i])) {
00922             if (isset($urlInv[$defaults[$i]])) {
00923                 if (!in_array($defaults[$i], $url) && is_int($urlInv[$defaults[$i]])) {
00924                     return false;
00925                 }
00926                 unset($urlInv[$defaults[$i]], $defaults[$i]);
00927             } else {
00928                 return false;
00929             }
00930             $i++;
00931         }
00932 
00933         foreach ($params as $key => $value) {
00934             if (is_int($key)) {
00935                 $pass[] = $value;
00936                 unset($params[$key]);
00937             }
00938         }
00939         list($named, $params) = $_this->getNamedElements($params);
00940 
00941         if (!strpos($route[0], '*') && (!empty($pass) || !empty($named))) {
00942             return false;
00943         }
00944 
00945         $urlKeys = array_keys($url);
00946         $paramsKeys = array_keys($params);
00947         $defaultsKeys = array_keys($defaults);
00948 
00949         if (!empty($params)) {
00950             if (array_diff($paramsKeys, $routeParams) != array()) {
00951                 return false;
00952             }
00953             $required = array_values(array_diff($routeParams, $urlKeys));
00954             $reqCount = count($required);
00955 
00956             for ($i = 0; $i < $reqCount; $i++) {
00957                 if (array_key_exists($required[$i], $defaults) && $defaults[$required[$i]] === null) {
00958                     unset($required[$i]);
00959                 }
00960             }
00961         }
00962         $isFilled = true;
00963 
00964         if (!empty($routeParams)) {
00965             $filled = array_intersect_key($url, array_combine($routeParams, array_keys($routeParams)));
00966             $isFilled = (array_diff($routeParams, array_keys($filled)) == array());
00967             if (!$isFilled && empty($params)) {
00968                 return false;
00969             }
00970         }
00971 
00972         if (empty($params)) {
00973             return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
00974         } elseif (!empty($routeParams) && !empty($route[3])) {
00975 
00976             if (!empty($required)) {
00977                 return false;
00978             }
00979             foreach ($params as $key => $val) {
00980                 if ((!isset($url[$key]) || $url[$key] != $val) || (!isset($defaults[$key]) || $defaults[$key] != $val) && !in_array($key, $routeParams)) {
00981                     //if (array_key_exists($key, $defaults) && $defaults[$key] === null) {
00982                     if (!isset($defaults[$key])) {
00983                         continue;
00984                     }
00985                     return false;
00986                 }
00987             }
00988         } else {
00989             if (empty($required) && $defaults['plugin'] == $url['plugin'] && $defaults['controller'] == $url['controller'] && $defaults['action'] == $url['action']) {
00990                 return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
00991             }
00992             return false;
00993         }
00994 
00995         if (!empty($route[4])) {
00996             foreach ($route[4] as $key => $reg) {
00997                 if (array_key_exists($key, $url) && !preg_match('#' . $reg . '#', $url[$key])) {
00998                     return false;
00999                 }
01000             }
01001         }
01002         return Router::__mapRoute($route, array_merge($filled, compact('pass', 'named', 'prefix')));
01003     }
01004 /**
01005  * Merges URL parameters into a route string
01006  *
01007  * @param array $route Route
01008  * @param array $params Parameters
01009  * @return string Merged URL with parameters
01010  * @access private
01011  */
01012     function __mapRoute($route, $params = array()) {
01013         $_this =& Router::getInstance();
01014 
01015         if(isset($params['plugin']) && isset($params['controller']) && $params['plugin'] === $params['controller']) {
01016             unset($params['controller']);
01017         }
01018 
01019         if (isset($params['prefix']) && isset($params['action'])) {
01020             $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
01021             unset($params['prefix']);
01022         }
01023 
01024         if (isset($params['pass']) && is_array($params['pass'])) {
01025             $params['pass'] = implode('/', Set::filter($params['pass'], true));
01026         } elseif (!isset($params['pass'])) {
01027             $params['pass'] = '';
01028         }
01029 
01030         if (isset($params['named'])) {
01031             if (is_array($params['named'])) {
01032                 $count = count($params['named']);
01033                 $keys = array_keys($params['named']);
01034                 $named = array();
01035 
01036                 for ($i = 0; $i < $count; $i++) {
01037                     $named[] = $keys[$i] . $_this->named['separator'] . $params['named'][$keys[$i]];
01038                 }
01039                 $params['named'] = join('/', $named);
01040             }
01041             $params['pass'] = str_replace('//', '/', $params['pass'] . '/' . $params['named']);
01042         }
01043         $out = $route[0];
01044 
01045         foreach ($route[2] as $key) {
01046             $string = null;
01047             if (isset($params[$key])) {
01048                 $string = $params[$key];
01049                 unset($params[$key]);
01050             }
01051             $out = str_replace(':' . $key, $string, $out);
01052         }
01053 
01054         if (strpos($route[0], '*')) {
01055             $out = str_replace('*', $params['pass'], $out);
01056         }
01057         return $out;
01058     }
01059 /**
01060  * Takes an array of URL parameters and separates the ones that can be used as named arguments
01061  *
01062  * @param array $params         Associative array of URL parameters.
01063  * @param string $controller    Name of controller being routed.  Used in scoping.
01064  * @param string $action        Name of action being routed.  Used in scoping.
01065  * @return array
01066  * @access public
01067  * @static
01068  */
01069     function getNamedElements($params, $controller = null, $action = null) {
01070         $_this =& Router::getInstance();
01071         $named = array();
01072 
01073         foreach ($params as $param => $val) {
01074             if (isset($_this->named['rules'][$param])) {
01075                 $rule = $_this->named['rules'][$param];
01076                 if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) {
01077                     $named[$param] = $val;
01078                     unset($params[$param]);
01079                 }
01080             }
01081         }
01082         return array($named, $params);
01083     }
01084 /**
01085  * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented
01086  * rule types are controller, action and match that can be combined with each other.
01087  *
01088  * @param string $param The name of the named parameter
01089  * @param string $val The value of the named parameter
01090  * @param array $rule The rule(s) to apply, can also be a match string
01091  * @param string $context An array with additional context information (controller / action)
01092  * @return boolean
01093  * @access public
01094  */
01095     function matchNamed($param, $val, $rule, $context = array()) {
01096         if ($rule === true || $rule === false) {
01097             return $rule;
01098         }
01099         if (is_string($rule)) {
01100             $rule = array('match' => $rule);
01101         }
01102         if (!is_array($rule)) {
01103             return false;
01104         }
01105 
01106         $controllerMatches = !isset($rule['controller'], $context['controller']) || in_array($context['controller'], (array)$rule['controller']);
01107         if (!$controllerMatches) {
01108             return false;
01109         }
01110         $actionMatches = !isset($rule['action'], $context['action']) || in_array($context['action'], (array)$rule['action']);
01111         if (!$actionMatches) {
01112             return false;
01113         }
01114         $valueMatches = !isset($rule['match']) || preg_match(sprintf('/%s/', $rule['match']), $val);
01115         return $valueMatches;
01116     }
01117 /**
01118  * Generates a well-formed querystring from $q
01119  *
01120  * @param mixed $q Query string
01121  * @param array $extra Extra querystring parameters
01122  * @return array
01123  * @access public
01124  * @static
01125  */
01126     function queryString($q, $extra = array()) {
01127         if (empty($q) && empty($extra)) {
01128             return null;
01129         }
01130         $out = '';
01131 
01132         if (is_array($q)) {
01133             $q = array_merge($extra, $q);
01134         } else {
01135             $out = $q;
01136             $q = $extra;
01137         }
01138         $out .= http_build_query($q, null, '&');
01139         if (isset($out[0]) && $out[0] != '?') {
01140             $out = '?' . $out;
01141         }
01142         return $out;
01143     }
01144 /**
01145  * Normalizes a URL for purposes of comparison
01146  *
01147  * @param mixed $url URL to normalize
01148  * @return string Normalized URL
01149  * @access public
01150  */
01151     function normalize($url = '/') {
01152         if (is_array($url)) {
01153             $url = Router::url($url);
01154         }
01155         $paths = Router::getPaths();
01156 
01157         if (!empty($paths['base']) && stristr($url, $paths['base'])) {
01158             $url = str_replace($paths['base'], '', $url);
01159         }
01160         $url = '/' . $url;
01161 
01162         while (strpos($url, '//') !== false) {
01163             $url = str_replace('//', '/', $url);
01164         }
01165         $url = preg_replace('/(\/$)/', '', $url);
01166 
01167         if (empty($url)) {
01168             return '/';
01169         }
01170         return $url;
01171     }
01172 /**
01173  * Returns the route matching the current request URL.
01174  *
01175  * @return array Matching route
01176  * @access public
01177  * @static
01178  */
01179     function requestRoute() {
01180         $_this =& Router::getInstance();
01181         return $_this->__currentRoute[0];
01182     }
01183 /**
01184  * Returns the route matching the current request (useful for requestAction traces)
01185  *
01186  * @return array Matching route
01187  * @access public
01188  * @static
01189  */
01190     function currentRoute() {
01191         $_this =& Router::getInstance();
01192         return $_this->__currentRoute[count($_this->__currentRoute) - 1];
01193     }
01194 /**
01195  * Removes the plugin name from the base URL.
01196  *
01197  * @param string $base Base URL
01198  * @param string $plugin Plugin name
01199  * @return base url with plugin name removed if present
01200  * @access public
01201  * @static
01202  */
01203     function stripPlugin($base, $plugin) {
01204         if ($plugin != null) {
01205             $base = preg_replace('/' . $plugin . '/', '', $base);
01206             $base = str_replace('//', '', $base);
01207             $pos1 = strrpos($base, '/');
01208             $char = strlen($base) - 1;
01209 
01210             if ($pos1 == $char) {
01211                 $base = substr($base, 0, $char);
01212             }
01213         }
01214         return $base;
01215     }
01216 
01217 /**
01218  * Strip escape characters from parameter values.
01219  *
01220  * @param mixed $param Either an array, or a string
01221  * @return mixed Array or string escaped
01222  * @access public
01223  * @static
01224  */
01225     function stripEscape($param) {
01226         $_this =& Router::getInstance();
01227         if (!is_array($param) || empty($param)) {
01228             if (is_bool($param)) {
01229                 return $param;
01230             }
01231 
01232             $return = preg_replace('/^[\\t ]*(?:-!)+/', '', $param);
01233             return $return;
01234         }
01235         foreach ($param as $key => $value) {
01236             if (is_string($value)) {
01237                 $return[$key] = preg_replace('/^[\\t ]*(?:-!)+/', '', $value);
01238             } else {
01239                 foreach ($value as $array => $string) {
01240                     $return[$key][$array] = $_this->stripEscape($string);
01241                 }
01242             }
01243         }
01244         return $return;
01245     }
01246 /**
01247  * Instructs the router to parse out file extensions from the URL. For example,
01248  * http://example.com/posts.rss would yield an file extension of "rss".
01249  * The file extension itself is made available in the controller as
01250  * $this->params['url']['ext'], and is used by the RequestHandler component to
01251  * automatically switch to alternate layouts and templates, and load helpers
01252  * corresponding to the given content, i.e. RssHelper.
01253  *
01254  * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
01255  * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
01256  * parsed, excluding querystring parameters (i.e. ?q=...).
01257  *
01258  * @access public
01259  * @static
01260  */
01261     function parseExtensions() {
01262         $_this =& Router::getInstance();
01263         $_this->__parseExtensions = true;
01264         if (func_num_args() > 0) {
01265             $_this->__validExtensions = func_get_args();
01266         }
01267     }
01268 /**
01269  * Takes an passed params and converts it to args
01270  *
01271  * @access public
01272  * @param array $params
01273  * @static
01274  */
01275     function getArgs($args, $options = array()) {
01276         $_this =& Router::getInstance();
01277         $pass = $named = array();
01278         $args = explode('/', $args);
01279 
01280         $greedy = $_this->named['greedy'];
01281         if (isset($options['greedy'])) {
01282             $greedy = $options['greedy'];
01283         }
01284         $context = array();
01285         if (isset($options['context'])) {
01286             $context = $options['context'];
01287         }
01288         $rules = $_this->named['rules'];
01289         if (isset($options['named'])) {
01290             $greedy = isset($options['greedy']) && $options['greedy'] == true;
01291             foreach ((array)$options['named'] as $key => $val) {
01292                 if (is_numeric($key)) {
01293                     $rules[$val] = true;
01294                     continue;
01295                 }
01296                 $rules[$key] = $val;
01297             }
01298         }
01299 
01300         foreach ($args as $param) {
01301             if (empty($param) && $param !== '0' && $param !== 0) {
01302                 continue;
01303             }
01304             $param = $_this->stripEscape($param);
01305             if ((!isset($options['named']) || !empty($options['named'])) && strpos($param, $_this->named['separator']) !== false) {
01306                 list($key, $val) = explode($_this->named['separator'], $param, 2);
01307                 $hasRule = isset($rules[$key]);
01308                 $passIt = (!$hasRule && !$greedy) || ($hasRule && !Router::matchNamed($key, $val, $rules[$key], $context));
01309                 if ($passIt) {
01310                     $pass[] = $param;
01311                 } else {
01312                     $named[$key] = $val;
01313                 }
01314             } else {
01315                 $pass[] = $param;
01316             }
01317         }
01318         return compact('pass', 'named');
01319     }
01320 }
01321 ?>