From 6dfd5d507d9863f987b30b0a5ab4268aea2ed875 Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Thu, 2 Aug 2012 11:09:40 +0000 Subject: J'étais parti sur un download pourri de Cake. Les gars on abusé sur GitHub. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: file:///var/svn/2012-php-weave/trunk@7 d972a294-176a-4cf9-8ea1-fcd5b0c30f5c --- .../lib/Cake/Routing/Dispatcher.php | 281 +++++ .../lib/Cake/Routing/DispatcherFilter.php | 86 ++ .../lib/Cake/Routing/Filter/AssetDispatcher.php | 159 +++ .../lib/Cake/Routing/Filter/CacheDispatcher.php | 67 ++ .../lib/Cake/Routing/Route/CakeRoute.php | 532 +++++++++ .../lib/Cake/Routing/Route/PluginShortRoute.php | 58 + .../lib/Cake/Routing/Route/RedirectRoute.php | 117 ++ .../lib/Cake/Routing/Router.php | 1141 ++++++++++++++++++++ 8 files changed, 2441 insertions(+) create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php create mode 100644 poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php (limited to 'poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing') diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php new file mode 100644 index 0000000..bf3e395 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php @@ -0,0 +1,281 @@ +_eventManager) { + $this->_eventManager = new CakeEventManager(); + $this->_eventManager->attach($this); + $this->_attachFilters($this->_eventManager); + } + return $this->_eventManager; + } + +/** + * Returns the list of events this object listents to. + * + * @return array + */ + public function implementedEvents() { + return array('Dispatcher.beforeDispatch' => 'parseParams'); + } + +/** + * Attaches all event listeners for this dispatcher instance. Loads the + * dispatcher filters from the configured locations. + * + * @param CakeEventManager $manager + * @return void + * @throws MissingDispatcherFilterException + */ + protected function _attachFilters($manager) { + $filters = Configure::read('Dispatcher.filters'); + if (empty($filters)) { + return; + } + + foreach ($filters as $filter) { + if (is_string($filter)) { + $filter = array('callable' => $filter); + } + if (is_string($filter['callable'])) { + list($plugin, $callable) = pluginSplit($filter['callable'], true); + App::uses($callable, $plugin . 'Routing/Filter'); + if (!class_exists($callable)) { + throw new MissingDispatcherFilterException($callable); + } + $manager->attach(new $callable); + } else { + $on = strtolower($filter['on']); + $options = array(); + if (isset($filter['priority'])) { + $options = array('priority' => $filter['priority']); + } + $manager->attach($filter['callable'], 'Dispatcher.' . $on . 'Dispatch', $options); + } + } + } + +/** + * Dispatches and invokes given Request, handing over control to the involved controller. If the controller is set + * to autoRender, via Controller::$autoRender, then Dispatcher will render the view. + * + * Actions in CakePHP can be any public method on a controller, that is not declared in Controller. If you + * want controller methods to be public and in-accessible by URL, then prefix them with a `_`. + * For example `public function _loadPosts() { }` would not be accessible via URL. Private and protected methods + * are also not accessible via URL. + * + * If no controller of given name can be found, invoke() will throw an exception. + * If the controller is found, and the action is not found an exception will be thrown. + * + * @param CakeRequest $request Request object to dispatch. + * @param CakeResponse $response Response object to put the results of the dispatch into. + * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params + * @return string|void if `$request['return']` is set then it returns response body, null otherwise + * @throws MissingControllerException When the controller is missing. + */ + public function dispatch(CakeRequest $request, CakeResponse $response, $additionalParams = array()) { + $beforeEvent = new CakeEvent('Dispatcher.beforeDispatch', $this, compact('request', 'response', 'additionalParams')); + $this->getEventManager()->dispatch($beforeEvent); + + $request = $beforeEvent->data['request']; + if ($beforeEvent->result instanceof CakeResponse) { + if (isset($request->params['return'])) { + return $response->body(); + } + $response->send(); + return; + } + + $controller = $this->_getController($request, $response); + + if (!($controller instanceof Controller)) { + throw new MissingControllerException(array( + 'class' => Inflector::camelize($request->params['controller']) . 'Controller', + 'plugin' => empty($request->params['plugin']) ? null : Inflector::camelize($request->params['plugin']) + )); + } + + $response = $this->_invoke($controller, $request, $response); + if (isset($request->params['return'])) { + return $response->body(); + } + + $afterEvent = new CakeEvent('Dispatcher.afterDispatch', $this, compact('request', 'response')); + $this->getEventManager()->dispatch($afterEvent); + $afterEvent->data['response']->send(); + } + +/** + * Initializes the components and models a controller will be using. + * Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output. + * Otherwise the return value of the controller action are returned. + * + * @param Controller $controller Controller to invoke + * @param CakeRequest $request The request object to invoke the controller for. + * @param CakeResponse $response The response object to receive the output + * @return CakeResponse te resulting response object + */ + protected function _invoke(Controller $controller, CakeRequest $request, CakeResponse $response) { + $controller->constructClasses(); + $controller->startupProcess(); + + $render = true; + $result = $controller->invokeAction($request); + if ($result instanceof CakeResponse) { + $render = false; + $response = $result; + } + + if ($render && $controller->autoRender) { + $response = $controller->render(); + } elseif ($response->body() === null) { + $response->body($result); + } + $controller->shutdownProcess(); + + return $response; + } + +/** + * Applies Routing and additionalParameters to the request to be dispatched. + * If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run. + * + * @param CakeEvent $event containing the request, response and additional params + * @return void + */ + public function parseParams($event) { + $request = $event->data['request']; + Router::setRequestInfo($request); + if (count(Router::$routes) == 0) { + $namedExpressions = Router::getNamedExpressions(); + extract($namedExpressions); + $this->_loadRoutes(); + } + + $params = Router::parse($request->url); + $request->addParams($params); + + if (!empty($event->data['additionalParams'])) { + $request->addParams($event->data['additionalParams']); + } + } + +/** + * Get controller to use, either plugin controller or application controller + * + * @param CakeRequest $request Request object + * @param CakeResponse $response Response for the controller. + * @return mixed name of controller if not loaded, or object if loaded + */ + protected function _getController($request, $response) { + $ctrlClass = $this->_loadController($request); + if (!$ctrlClass) { + return false; + } + $reflection = new ReflectionClass($ctrlClass); + if ($reflection->isAbstract() || $reflection->isInterface()) { + return false; + } + return $reflection->newInstance($request, $response); + } + +/** + * Load controller and return controller classname + * + * @param CakeRequest $request + * @return string|bool Name of controller class name + */ + protected function _loadController($request) { + $pluginName = $pluginPath = $controller = null; + if (!empty($request->params['plugin'])) { + $pluginName = $controller = Inflector::camelize($request->params['plugin']); + $pluginPath = $pluginName . '.'; + } + if (!empty($request->params['controller'])) { + $controller = Inflector::camelize($request->params['controller']); + } + if ($pluginPath . $controller) { + $class = $controller . 'Controller'; + App::uses('AppController', 'Controller'); + App::uses($pluginName . 'AppController', $pluginPath . 'Controller'); + App::uses($class, $pluginPath . 'Controller'); + if (class_exists($class)) { + return $class; + } + } + return false; + } + +/** + * Loads route configuration + * + * @return void + */ + protected function _loadRoutes() { + include APP . 'Config' . DS . 'routes.php'; + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php new file mode 100644 index 0000000..cf6f4fa --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php @@ -0,0 +1,86 @@ + array('callable' => 'beforeDispatch', 'priority' => $this->priority), + 'Dispatcher.afterDispatch' => array('callable' => 'afterDispatch', 'priority' => $this->priority), + ); + } + +/** + * Method called before the controller is instantiated and called to ser a request. + * If used with default priority, it will be called after the Router has parsed the + * url and set the routing params into the request object. + * + * If a CakeResponse object instance is returned, it will be served at the end of the + * event cycle, not calling any controller as a result. This will also have the effect of + * not calling the after event in the dispatcher. + * + * If false is returned, the event will be stopped and no more listeners will be notified. + * Alternatively you can call `$event->stopPropagation()` to acheive the same result. + * + * @param CakeEvent $event container object having the `request`, `response` and `additionalParams` + * keys in the data property. + * @return CakeResponse|boolean + **/ + public function beforeDispatch($event) { + } + +/** + * Method called after the controller served a request and generated a response. + * It is posible to alter the response object at this point as it is not sent to the + * client yet. + * + * If false is returned, the event will be stopped and no more listeners will be notified. + * Alternatively you can call `$event->stopPropagation()` to acheive the same result. + * + * @param CakeEvent $event container object having the `request` and `response` + * keys in the data property. + * @return mixed boolean to stop the event dispatching or null to continue + **/ + public function afterDispatch($event) { + } +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php new file mode 100644 index 0000000..11840ba --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php @@ -0,0 +1,159 @@ +data['request']->url; + $response = $event->data['response']; + + if (strpos($url, '..') !== false || strpos($url, '.') === false) { + return; + } + + if ($result = $this->_filterAsset($event)) { + $event->stopPropagation(); + return $result; + } + + $pathSegments = explode('.', $url); + $ext = array_pop($pathSegments); + $parts = explode('/', $url); + $assetFile = null; + + if ($parts[0] === 'theme') { + $themeName = $parts[1]; + unset($parts[0], $parts[1]); + $fileFragment = urldecode(implode(DS, $parts)); + $path = App::themePath($themeName) . 'webroot' . DS; + if (file_exists($path . $fileFragment)) { + $assetFile = $path . $fileFragment; + } + } else { + $plugin = Inflector::camelize($parts[0]); + if (CakePlugin::loaded($plugin)) { + unset($parts[0]); + $fileFragment = urldecode(implode(DS, $parts)); + $pluginWebroot = CakePlugin::path($plugin) . 'webroot' . DS; + if (file_exists($pluginWebroot . $fileFragment)) { + $assetFile = $pluginWebroot . $fileFragment; + } + } + } + + if ($assetFile !== null) { + $event->stopPropagation(); + $response->modified(filemtime($assetFile)); + if (!$response->checkNotModified($event->data['request'])) { + $this->_deliverAsset($response, $assetFile, $ext); + } + return $response; + } + } + +/** + * Checks if the client is requeting a filtered asset and runs the corresponding + * filter if any is configured + * + * @param CakeEvent $event containing the request and response object + * @return CakeResponse if the client is requesting a recognized asset, null otherwise + */ + protected function _filterAsset($event) { + $url = $event->data['request']->url; + $response = $event->data['response']; + $filters = Configure::read('Asset.filter'); + $isCss = ( + strpos($url, 'ccss/') === 0 || + preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?statusCode(404); + return $response; + } elseif ($isCss) { + include WWW_ROOT . DS . $filters['css']; + return $response; + } elseif ($isJs) { + include WWW_ROOT . DS . $filters['js']; + return $response; + } + } + +/** + * Sends an asset file to the client + * + * @param CakeResponse $response The response object to use. + * @param string $assetFile Path to the asset file in the file system + * @param string $ext The extension of the file to determine its mime type + * @return void + */ + protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) { + ob_start(); + $compressionEnabled = Configure::read('Asset.compress') && $response->compress(); + if ($response->type($ext) == $ext) { + $contentType = 'application/octet-stream'; + $agent = env('HTTP_USER_AGENT'); + if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { + $contentType = 'application/octetstream'; + } + $response->type($contentType); + } + if (!$compressionEnabled) { + $response->header('Content-Length', filesize($assetFile)); + } + $response->cache(filemtime($assetFile)); + $response->send(); + ob_clean(); + if ($ext === 'css' || $ext === 'js') { + include $assetFile; + } else { + readfile($assetFile); + } + + if ($compressionEnabled) { + ob_end_flush(); + } + } + +} \ No newline at end of file diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php new file mode 100644 index 0000000..908d77a --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php @@ -0,0 +1,67 @@ +data['request']->here(); + if ($path == '/') { + $path = 'home'; + } + $path = strtolower(Inflector::slug($path)); + + $filename = CACHE . 'views' . DS . $path . '.php'; + + if (!file_exists($filename)) { + $filename = CACHE . 'views' . DS . $path . '_index.php'; + } + if (file_exists($filename)) { + $controller = null; + $view = new View($controller); + $result = $view->renderCache($filename, microtime(true)); + if ($result !== false) { + $event->stopPropagation(); + $event->data['response']->body($result); + return $event->data['response']; + } + } + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php new file mode 100644 index 0000000..8fab212 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php @@ -0,0 +1,532 @@ + 'content_type', + 'method' => 'request_method', + 'server' => 'server_name' + ); + +/** + * Constructor for a Route + * + * @param string $template Template string with parameter placeholders + * @param array $defaults Array of defaults for the route. + * @param array $options Array of additional options for the Route + */ + public function __construct($template, $defaults = array(), $options = array()) { + $this->template = $template; + $this->defaults = (array)$defaults; + $this->options = (array)$options; + } + +/** + * Check if a Route has been compiled into a regular expression. + * + * @return boolean + */ + public function compiled() { + return !empty($this->_compiledRoute); + } + +/** + * Compiles the route's regular expression. Modifies defaults property so all necessary keys are set + * and populates $this->names with the named routing elements. + * + * @return array Returns a string regular expression of the compiled route. + */ + public function compile() { + if ($this->compiled()) { + return $this->_compiledRoute; + } + $this->_writeRoute(); + return $this->_compiledRoute; + } + +/** + * Builds a route regular expression. Uses the template, defaults and options + * properties to compile a regular expression that can be used to parse request strings. + * + * @return void + */ + protected function _writeRoute() { + if (empty($this->template) || ($this->template === '/')) { + $this->_compiledRoute = '#^/*$#'; + $this->keys = array(); + return; + } + $route = $this->template; + $names = $routeParams = array(); + $parsed = preg_quote($this->template, '#'); + + preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $route, $namedElements); + foreach ($namedElements[1] as $i => $name) { + $search = '\\' . $namedElements[0][$i]; + if (isset($this->options[$name])) { + $option = null; + if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) { + $option = '?'; + } + $slashParam = '/\\' . $namedElements[0][$i]; + if (strpos($parsed, $slashParam) !== false) { + $routeParams[$slashParam] = '(?:/(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option; + } else { + $routeParams[$search] = '(?:(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option; + } + } else { + $routeParams[$search] = '(?:(?P<' . $name . '>[^/]+))'; + } + $names[] = $name; + } + if (preg_match('#\/\*\*$#', $route)) { + $parsed = preg_replace('#/\\\\\*\\\\\*$#', '(?:/(?P<_trailing_>.*))?', $parsed); + $this->_greedy = true; + } elseif (preg_match('#\/\*$#', $route)) { + $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed); + $this->_greedy = true; + } + krsort($routeParams); + $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed); + $this->_compiledRoute = '#^' . $parsed . '[/]*$#'; + $this->keys = $names; + + //remove defaults that are also keys. They can cause match failures + foreach ($this->keys as $key) { + unset($this->defaults[$key]); + } + } + +/** + * Checks to see if the given URL can be parsed by this route. + * If the route can be parsed an array of parameters will be returned; if not + * false will be returned. String urls are parsed if they match a routes regular expression. + * + * @param string $url The url to attempt to parse. + * @return mixed Boolean false on failure, otherwise an array or parameters + */ + public function parse($url) { + if (!$this->compiled()) { + $this->compile(); + } + if (!preg_match($this->_compiledRoute, $url, $route)) { + return false; + } + foreach ($this->defaults as $key => $val) { + $key = (string)$key; + if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) { + if (isset($this->_headerMap[$header[1]])) { + $header = $this->_headerMap[$header[1]]; + } else { + $header = 'http_' . $header[1]; + } + $header = strtoupper($header); + + $val = (array)$val; + $h = false; + + foreach ($val as $v) { + if (env($header) === $v) { + $h = true; + } + } + if (!$h) { + return false; + } + } + } + array_shift($route); + $count = count($this->keys); + for ($i = 0; $i <= $count; $i++) { + unset($route[$i]); + } + $route['pass'] = $route['named'] = array(); + + // Assign defaults, set passed args to pass + foreach ($this->defaults as $key => $value) { + if (isset($route[$key])) { + continue; + } + if (is_integer($key)) { + $route['pass'][] = $value; + continue; + } + $route[$key] = $value; + } + + foreach ($this->keys as $key) { + if (isset($route[$key])) { + $route[$key] = rawurldecode($route[$key]); + } + } + + if (isset($route['_args_'])) { + list($pass, $named) = $this->_parseArgs($route['_args_'], $route); + $route['pass'] = array_merge($route['pass'], $pass); + $route['named'] = $named; + unset($route['_args_']); + } + + if (isset($route['_trailing_'])) { + $route['pass'][] = rawurldecode($route['_trailing_']); + unset($route['_trailing_']); + } + + // restructure 'pass' key route params + if (isset($this->options['pass'])) { + $j = count($this->options['pass']); + while ($j--) { + if (isset($route[$this->options['pass'][$j]])) { + array_unshift($route['pass'], $route[$this->options['pass'][$j]]); + } + } + } + return $route; + } + +/** + * Parse passed and Named parameters into a list of passed args, and a hash of named parameters. + * The local and global configuration for named parameters will be used. + * + * @param string $args A string with the passed & named params. eg. /1/page:2 + * @param string $context The current route context, which should contain controller/action keys. + * @return array Array of ($pass, $named) + */ + protected function _parseArgs($args, $context) { + $pass = $named = array(); + $args = explode('/', $args); + + $namedConfig = Router::namedConfig(); + $greedy = $namedConfig['greedyNamed']; + $rules = $namedConfig['rules']; + if (!empty($this->options['named'])) { + $greedy = isset($this->options['greedyNamed']) && $this->options['greedyNamed'] === true; + foreach ((array)$this->options['named'] as $key => $val) { + if (is_numeric($key)) { + $rules[$val] = true; + continue; + } + $rules[$key] = $val; + } + } + + foreach ($args as $param) { + if (empty($param) && $param !== '0' && $param !== 0) { + continue; + } + + $separatorIsPresent = strpos($param, $namedConfig['separator']) !== false; + if ((!isset($this->options['named']) || !empty($this->options['named'])) && $separatorIsPresent) { + list($key, $val) = explode($namedConfig['separator'], $param, 2); + $key = rawurldecode($key); + $val = rawurldecode($val); + $hasRule = isset($rules[$key]); + $passIt = (!$hasRule && !$greedy) || ($hasRule && !$this->_matchNamed($val, $rules[$key], $context)); + if ($passIt) { + $pass[] = rawurldecode($param); + } else { + if (preg_match_all('/\[([A-Za-z0-9_-]+)?\]/', $key, $matches, PREG_SET_ORDER)) { + $matches = array_reverse($matches); + $parts = explode('[', $key); + $key = array_shift($parts); + $arr = $val; + foreach ($matches as $match) { + if (empty($match[1])) { + $arr = array($arr); + } else { + $arr = array( + $match[1] => $arr + ); + } + } + $val = $arr; + } + $named = array_merge_recursive($named, array($key => $val)); + } + } else { + $pass[] = rawurldecode($param); + } + } + return array($pass, $named); + } + +/** + * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented + * rule types are controller, action and match that can be combined with each other. + * + * @param string $val The value of the named parameter + * @param array $rule The rule(s) to apply, can also be a match string + * @param string $context An array with additional context information (controller / action) + * @return boolean + */ + protected function _matchNamed($val, $rule, $context) { + if ($rule === true || $rule === false) { + return $rule; + } + if (is_string($rule)) { + $rule = array('match' => $rule); + } + if (!is_array($rule)) { + return false; + } + + $controllerMatches = ( + !isset($rule['controller'], $context['controller']) || + in_array($context['controller'], (array)$rule['controller']) + ); + if (!$controllerMatches) { + return false; + } + $actionMatches = ( + !isset($rule['action'], $context['action']) || + in_array($context['action'], (array)$rule['action']) + ); + if (!$actionMatches) { + return false; + } + return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val)); + } + +/** + * Apply persistent parameters to a url array. Persistent parameters are a special + * key used during route creation to force route parameters to persist when omitted from + * a url array. + * + * @param array $url The array to apply persistent parameters to. + * @param array $params An array of persistent values to replace persistent ones. + * @return array An array with persistent parameters applied. + */ + public function persistParams($url, $params) { + foreach ($this->options['persist'] as $persistKey) { + if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) { + $url[$persistKey] = $params[$persistKey]; + } + } + return $url; + } + +/** + * Attempt to match a url array. If the url matches the route parameters and settings, then + * return a generated string url. If the url doesn't match the route parameters, false will be returned. + * This method handles the reverse routing or conversion of url arrays into string urls. + * + * @param array $url An array of parameters to check matching with. + * @return mixed Either a string url for the parameters if they match or false. + */ + public function match($url) { + if (!$this->compiled()) { + $this->compile(); + } + $defaults = $this->defaults; + + if (isset($defaults['prefix'])) { + $url['prefix'] = $defaults['prefix']; + } + + //check that all the key names are in the url + $keyNames = array_flip($this->keys); + if (array_intersect_key($keyNames, $url) !== $keyNames) { + return false; + } + + // Missing defaults is a fail. + if (array_diff_key($defaults, $url) !== array()) { + return false; + } + + $namedConfig = Router::namedConfig(); + $prefixes = Router::prefixes(); + $greedyNamed = $namedConfig['greedyNamed']; + $allowedNamedParams = $namedConfig['rules']; + + $named = $pass = array(); + + foreach ($url as $key => $value) { + + // keys that exist in the defaults and have different values is a match failure. + $defaultExists = array_key_exists($key, $defaults); + if ($defaultExists && $defaults[$key] != $value) { + return false; + } elseif ($defaultExists) { + continue; + } + + // If the key is a routed key, its not different yet. + if (array_key_exists($key, $keyNames)) { + continue; + } + + // pull out passed args + $numeric = is_numeric($key); + if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) { + continue; + } elseif ($numeric) { + $pass[] = $value; + unset($url[$key]); + continue; + } + + // pull out named params if named params are greedy or a rule exists. + if ( + ($greedyNamed || isset($allowedNamedParams[$key])) && + ($value !== false && $value !== null) && + (!in_array($key, $prefixes)) + ) { + $named[$key] = $value; + continue; + } + + // keys that don't exist are different. + if (!$defaultExists && !empty($value)) { + return false; + } + } + + //if a not a greedy route, no extra params are allowed. + if (!$this->_greedy && (!empty($pass) || !empty($named))) { + return false; + } + + //check patterns for routed params + if (!empty($this->options)) { + foreach ($this->options as $key => $pattern) { + if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) { + return false; + } + } + } + return $this->_writeUrl(array_merge($url, compact('pass', 'named'))); + } + +/** + * Converts a matching route array into a url string. Composes the string url using the template + * used to create the route. + * + * @param array $params The params to convert to a string url. + * @return string Composed route string. + */ + protected function _writeUrl($params) { + if (isset($params['prefix'])) { + $prefixed = $params['prefix'] . '_'; + } + if (isset($prefixed, $params['action']) && strpos($params['action'], $prefixed) === 0) { + $params['action'] = substr($params['action'], strlen($prefixed) * -1); + unset($params['prefix']); + } + + if (is_array($params['pass'])) { + $params['pass'] = implode('/', array_map('rawurlencode', $params['pass'])); + } + + $namedConfig = Router::namedConfig(); + $separator = $namedConfig['separator']; + + if (!empty($params['named']) && is_array($params['named'])) { + $named = array(); + foreach ($params['named'] as $key => $value) { + if (is_array($value)) { + $flat = Hash::flatten($value, '%5D%5B'); + foreach ($flat as $namedKey => $namedValue) { + $named[] = $key . "%5B{$namedKey}%5D" . $separator . rawurlencode($namedValue); + } + } else { + $named[] = $key . $separator . rawurlencode($value); + } + } + $params['pass'] = $params['pass'] . '/' . implode('/', $named); + } + $out = $this->template; + + $search = $replace = array(); + foreach ($this->keys as $key) { + $string = null; + if (isset($params[$key])) { + $string = $params[$key]; + } elseif (strpos($out, $key) != strlen($out) - strlen($key)) { + $key .= '/'; + } + $search[] = ':' . $key; + $replace[] = $string; + } + $out = str_replace($search, $replace, $out); + + if (strpos($this->template, '*')) { + $out = str_replace('*', $params['pass'], $out); + } + $out = str_replace('//', '/', $out); + return $out; + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php new file mode 100644 index 0000000..2edd944 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php @@ -0,0 +1,58 @@ +defaults['controller'] = $url['controller']; + $result = parent::match($url); + unset($this->defaults['controller']); + return $result; + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php new file mode 100644 index 0000000..bfb0f06 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php @@ -0,0 +1,117 @@ +redirect = (array)$defaults; + } + +/** + * Parses a string url into an array. Parsed urls will result in an automatic + * redirection + * + * @param string $url The url to parse + * @return boolean False on failure + */ + public function parse($url) { + $params = parent::parse($url); + if (!$params) { + return false; + } + if (!$this->response) { + $this->response = new CakeResponse(); + } + $redirect = $this->redirect; + if (count($this->redirect) == 1 && !isset($this->redirect['controller'])) { + $redirect = $this->redirect[0]; + } + if (isset($this->options['persist']) && is_array($redirect)) { + $redirect += array('named' => $params['named'], 'pass' => $params['pass'], 'url' => array()); + $redirect = Router::reverse($redirect); + } + $status = 301; + if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) { + $status = $this->options['status']; + } + $this->response->header(array('Location' => Router::url($redirect, true))); + $this->response->statusCode($status); + $this->response->send(); + $this->_stop(); + } + +/** + * There is no reverse routing redirection routes + * + * @param array $url Array of parameters to convert to a string. + * @return mixed either false or a string url. + */ + public function match($url) { + return false; + } + +/** + * Stop execution of the current script. Wraps exit() making + * testing easier. + * + * @param integer|string $status see http://php.net/exit for values + * @return void + */ + protected function _stop($code = 0) { + if ($this->stop) { + exit($code); + } + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php new file mode 100644 index 0000000..42a2937 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php @@ -0,0 +1,1141 @@ + Router::ACTION, + 'Year' => Router::YEAR, + 'Month' => Router::MONTH, + 'Day' => Router::DAY, + 'ID' => Router::ID, + 'UUID' => Router::UUID + ); + +/** + * Stores all information necessary to decide what named arguments are parsed under what conditions. + * + * @var string + */ + protected static $_namedConfig = array( + 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'), + 'greedyNamed' => true, + 'separator' => ':', + 'rules' => false, + ); + +/** + * The route matching the URL of the current request + * + * @var array + */ + protected static $_currentRoute = array(); + +/** + * Default HTTP request method => controller action map. + * + * @var array + */ + protected static $_resourceMap = array( + array('action' => 'index', 'method' => 'GET', 'id' => false), + array('action' => 'view', 'method' => 'GET', 'id' => true), + array('action' => 'add', 'method' => 'POST', 'id' => false), + array('action' => 'edit', 'method' => 'PUT', 'id' => true), + array('action' => 'delete', 'method' => 'DELETE', 'id' => true), + array('action' => 'edit', 'method' => 'POST', 'id' => true) + ); + +/** + * List of resource-mapped controllers + * + * @var array + */ + protected static $_resourceMapped = array(); + +/** + * Maintains the request object stack for the current request. + * This will contain more than one request object when requestAction is used. + * + * @var array + */ + protected static $_requests = array(); + +/** + * Initial state is populated the first time reload() is called which is at the bottom + * of this file. This is a cheat as get_class_vars() returns the value of static vars even if they + * have changed. + * + * @var array + */ + protected static $_initialState = array(); + +/** + * Default route class to use + * + * @var string + */ + protected static $_routeClass = 'CakeRoute'; + +/** + * Set the default route class to use or return the current one + * + * @param string $routeClass to set as default + * @return mixed void|string + * @throws RouterException + */ + public static function defaultRouteClass($routeClass = null) { + if (is_null($routeClass)) { + return self::$_routeClass; + } + + self::$_routeClass = self::_validateRouteClass($routeClass); + } + +/** + * Validates that the passed route class exists and is a subclass of CakeRoute + * + * @param $routeClass + * @return string + * @throws RouterException + */ + protected static function _validateRouteClass($routeClass) { + if (!class_exists($routeClass) || !is_subclass_of($routeClass, 'CakeRoute')) { + throw new RouterException(__d('cake_dev', 'Route classes must extend CakeRoute')); + } + return $routeClass; + } + +/** + * Sets the Routing prefixes. + * + * @return void + */ + protected static function _setPrefixes() { + $routing = Configure::read('Routing'); + if (!empty($routing['prefixes'])) { + self::$_prefixes = array_merge(self::$_prefixes, (array)$routing['prefixes']); + } + } + +/** + * Gets the named route elements for use in app/Config/routes.php + * + * @return array Named route elements + * @see Router::$_namedExpressions + */ + public static function getNamedExpressions() { + return self::$_namedExpressions; + } + +/** + * Resource map getter & setter. + * + * @param array $resourceMap Resource map + * @return mixed + * @see Router::$_resourceMap + */ + public static function resourceMap($resourceMap = null) { + if ($resourceMap === null) { + return self::$_resourceMap; + } + self::$_resourceMap = $resourceMap; + } + +/** + * Connects a new Route in the router. + * + * Routes are a way of connecting request urls to objects in your application. At their core routes + * are a set or regular expressions that are used to match requests to destinations. + * + * Examples: + * + * `Router::connect('/:controller/:action/*');` + * + * The first parameter will be used as a controller name while the second is used as the action name. + * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests + * like `/posts/edit/1/foo/bar`. + * + * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));` + * + * The above shows the use of route parameter defaults. And providing routing parameters for a static route. + * + * {{{ + * Router::connect( + * '/:lang/:controller/:action/:id', + * array(), + * array('id' => '[0-9]+', 'lang' => '[a-z]{3}') + * ); + * }}} + * + * Shows connecting a route with custom route parameters as well as providing patterns for those parameters. + * Patterns for routing parameters do not need capturing groups, as one will be added for each route params. + * + * $options offers four 'special' keys. `pass`, `named`, `persist` and `routeClass` + * have special meaning in the $options array. + * + * `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a + * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')` + * + * `persist` is used to define which route parameters should be automatically included when generating + * new urls. You can override persistent parameters by redefining them in a url or remove them by + * setting the parameter to `false`. Ex. `'persist' => array('lang')` + * + * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing, + * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'` + * + * `named` is used to configure named parameters at the route level. This key uses the same options + * as Router::connectNamed() + * + * @param string $route A string describing the template of the route + * @param array $defaults An array describing the default route parameters. These parameters will be used by default + * and can supply routing parameters that are not dynamic. See above. + * @param array $options An array matching the named elements in the route to regular expressions which that + * element should match. Also contains additional parameters such as which routed parameters should be + * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a + * custom routing class. + * @see routes + * @return array Array of routes + * @throws RouterException + */ + public static function connect($route, $defaults = array(), $options = array()) { + foreach (self::$_prefixes as $prefix) { + if (isset($defaults[$prefix])) { + if ($defaults[$prefix]) { + $defaults['prefix'] = $prefix; + } else { + unset($defaults[$prefix]); + } + break; + } + } + if (isset($defaults['prefix'])) { + self::$_prefixes[] = $defaults['prefix']; + self::$_prefixes = array_keys(array_flip(self::$_prefixes)); + } + $defaults += array('plugin' => null); + if (empty($options['action'])) { + $defaults += array('action' => 'index'); + } + $routeClass = self::$_routeClass; + if (isset($options['routeClass'])) { + $routeClass = self::_validateRouteClass($options['routeClass']); + unset($options['routeClass']); + } + if ($routeClass == 'RedirectRoute' && isset($defaults['redirect'])) { + $defaults = $defaults['redirect']; + } + self::$routes[] = new $routeClass($route, $defaults, $options); + return self::$routes; + } + +/** + * Connects a new redirection Route in the router. + * + * Redirection routes are different from normal routes as they perform an actual + * header redirection if a match is found. The redirection can occur within your + * application or redirect to an outside location. + * + * Examples: + * + * `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view', array('persist' => true)));` + * + * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the + * redirect destination allows you to use other routes to define where a url string should be redirected to. + * + * `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));` + * + * Redirects /posts/* to http://google.com with a HTTP status of 302 + * + * ### Options: + * + * - `status` Sets the HTTP status (default 301) + * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes, + * routes that end in `*` are greedy. As you can remap urls and not loose any passed/named args. + * + * @param string $route A string describing the template of the route + * @param array $url A url to redirect to. Can be a string or a Cake array-based url + * @param array $options An array matching the named elements in the route to regular expressions which that + * element should match. Also contains additional parameters such as which routed parameters should be + * shifted into the passed arguments. As well as supplying patterns for routing parameters. + * @see routes + * @return array Array of routes + */ + public static function redirect($route, $url, $options = array()) { + App::uses('RedirectRoute', 'Routing/Route'); + $options['routeClass'] = 'RedirectRoute'; + if (is_string($url)) { + $url = array('redirect' => $url); + } + return self::connect($route, $url, $options); + } + +/** + * Specifies what named parameters CakePHP should be parsing out of incoming urls. By default + * CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more + * control over how named parameters are parsed you can use one of the following setups: + * + * Do not parse any named parameters: + * + * {{{ Router::connectNamed(false); }}} + * + * Parse only default parameters used for CakePHP's pagination: + * + * {{{ Router::connectNamed(false, array('default' => true)); }}} + * + * Parse only the page parameter if its value is a number: + * + * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}} + * + * Parse only the page parameter no matter what. + * + * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}} + * + * Parse only the page parameter if the current action is 'index'. + * + * {{{ + * Router::connectNamed( + * array('page' => array('action' => 'index')), + * array('default' => false, 'greedy' => false) + * ); + * }}} + * + * Parse only the page parameter if the current action is 'index' and the controller is 'pages'. + * + * {{{ + * Router::connectNamed( + * array('page' => array('action' => 'index', 'controller' => 'pages')), + * array('default' => false, 'greedy' => false) + * ); + * }}} + * + * ### Options + * + * - `greedy` Setting this to true will make Router parse all named params. Setting it to false will + * parse only the connected named params. + * - `default` Set this to true to merge in the default set of named parameters. + * - `reset` Set to true to clear existing rules and start fresh. + * - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:` + * + * @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. + * @param array $options Allows to control all settings: separator, greedy, reset, default + * @return array + */ + public static function connectNamed($named, $options = array()) { + if (isset($options['separator'])) { + self::$_namedConfig['separator'] = $options['separator']; + unset($options['separator']); + } + + if ($named === true || $named === false) { + $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options); + $named = array(); + } else { + $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options); + } + + if ($options['reset'] == true || self::$_namedConfig['rules'] === false) { + self::$_namedConfig['rules'] = array(); + } + + if ($options['default']) { + $named = array_merge($named, self::$_namedConfig['default']); + } + + foreach ($named as $key => $val) { + if (is_numeric($key)) { + self::$_namedConfig['rules'][$val] = true; + } else { + self::$_namedConfig['rules'][$key] = $val; + } + } + self::$_namedConfig['greedyNamed'] = $options['greedy']; + return self::$_namedConfig; + } + +/** + * Gets the current named parameter configuration values. + * + * @return array + * @see Router::$_namedConfig + */ + public static function namedConfig() { + return self::$_namedConfig; + } + +/** + * Creates REST resource routes for the given controller(s). When creating resource routes + * for a plugin, by default the prefix will be changed to the lower_underscore version of the plugin + * name. By providing a prefix you can override this behavior. + * + * ### Options: + * + * - 'id' - The regular expression fragment to use when matching IDs. By default, matches + * integer values and UUIDs. + * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'. + * + * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems") + * @param array $options Options to use when generating REST routes + * @return array Array of mapped resources + */ + public static function mapResources($controller, $options = array()) { + $hasPrefix = isset($options['prefix']); + $options = array_merge(array( + 'prefix' => '/', + 'id' => self::ID . '|' . self::UUID + ), $options); + + $prefix = $options['prefix']; + + foreach ((array)$controller as $name) { + list($plugin, $name) = pluginSplit($name); + $urlName = Inflector::underscore($name); + $plugin = Inflector::underscore($plugin); + if ($plugin && !$hasPrefix) { + $prefix = '/' . $plugin . '/'; + } + + foreach (self::$_resourceMap as $params) { + $url = $prefix . $urlName . (($params['id']) ? '/:id' : ''); + + Router::connect($url, + array( + 'plugin' => $plugin, + 'controller' => $urlName, + 'action' => $params['action'], + '[method]' => $params['method'] + ), + array('id' => $options['id'], 'pass' => array('id')) + ); + } + self::$_resourceMapped[] = $urlName; + } + return self::$_resourceMapped; + } + +/** + * Returns the list of prefixes used in connected routes + * + * @return array A list of prefixes used in connected routes + */ + public static function prefixes() { + return self::$_prefixes; + } + +/** + * Parses given URL string. Returns 'routing' parameters for that url. + * + * @param string $url URL to be parsed + * @return array Parsed elements from URL + */ + public static function parse($url) { + $ext = null; + $out = array(); + + if ($url && strpos($url, '/') !== 0) { + $url = '/' . $url; + } + if (strpos($url, '?') !== false) { + $url = substr($url, 0, strpos($url, '?')); + } + + extract(self::_parseExtension($url)); + + for ($i = 0, $len = count(self::$routes); $i < $len; $i++) { + $route =& self::$routes[$i]; + + if (($r = $route->parse($url)) !== false) { + self::$_currentRoute[] =& $route; + $out = $r; + break; + } + } + if (isset($out['prefix'])) { + $out['action'] = $out['prefix'] . '_' . $out['action']; + } + + if (!empty($ext) && !isset($out['ext'])) { + $out['ext'] = $ext; + } + return $out; + } + +/** + * Parses a file extension out of a URL, if Router::parseExtensions() is enabled. + * + * @param string $url + * @return array Returns an array containing the altered URL and the parsed extension. + */ + protected static function _parseExtension($url) { + $ext = null; + + if (self::$_parseExtensions) { + if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) { + $match = substr($match[0], 1); + if (empty(self::$_validExtensions)) { + $url = substr($url, 0, strpos($url, '.' . $match)); + $ext = $match; + } else { + foreach (self::$_validExtensions as $name) { + if (strcasecmp($name, $match) === 0) { + $url = substr($url, 0, strpos($url, '.' . $name)); + $ext = $match; + break; + } + } + } + } + } + return compact('ext', 'url'); + } + +/** + * Takes parameter and path information back from the Dispatcher, sets these + * parameters as the current request parameters that are merged with url arrays + * created later in the request. + * + * Nested requests will create a stack of requests. You can remove requests using + * Router::popRequest(). This is done automatically when using Object::requestAction(). + * + * Will accept either a CakeRequest object or an array of arrays. Support for + * accepting arrays may be removed in the future. + * + * @param CakeRequest|array $request Parameters and path information or a CakeRequest object. + * @return void + */ + public static function setRequestInfo($request) { + if ($request instanceof CakeRequest) { + self::$_requests[] = $request; + } else { + $requestObj = new CakeRequest(); + $request += array(array(), array()); + $request[0] += array('controller' => false, 'action' => false, 'plugin' => null); + $requestObj->addParams($request[0])->addPaths($request[1]); + self::$_requests[] = $requestObj; + } + } + +/** + * Pops a request off of the request stack. Used when doing requestAction + * + * @return CakeRequest The request removed from the stack. + * @see Router::setRequestInfo() + * @see Object::requestAction() + */ + public static function popRequest() { + return array_pop(self::$_requests); + } + +/** + * Get the either the current request object, or the first one. + * + * @param boolean $current Whether you want the request from the top of the stack or the first one. + * @return CakeRequest or null. + */ + public static function getRequest($current = false) { + if ($current) { + $i = count(self::$_requests) - 1; + return isset(self::$_requests[$i]) ? self::$_requests[$i] : null; + } + return isset(self::$_requests[0]) ? self::$_requests[0] : null; + } + +/** + * Gets parameter information + * + * @param boolean $current Get current request parameter, useful when using requestAction + * @return array Parameter information + */ + public static function getParams($current = false) { + if ($current) { + return self::$_requests[count(self::$_requests) - 1]->params; + } + if (isset(self::$_requests[0])) { + return self::$_requests[0]->params; + } + return array(); + } + +/** + * Gets URL parameter by name + * + * @param string $name Parameter name + * @param boolean $current Current parameter, useful when using requestAction + * @return string Parameter value + */ + public static function getParam($name = 'controller', $current = false) { + $params = Router::getParams($current); + if (isset($params[$name])) { + return $params[$name]; + } + return null; + } + +/** + * Gets path information + * + * @param boolean $current Current parameter, useful when using requestAction + * @return array + */ + public static function getPaths($current = false) { + if ($current) { + return self::$_requests[count(self::$_requests) - 1]; + } + if (!isset(self::$_requests[0])) { + return array('base' => null); + } + return array('base' => self::$_requests[0]->base); + } + +/** + * Reloads default Router settings. Resets all class variables and + * removes all connected routes. + * + * @return void + */ + public static function reload() { + if (empty(self::$_initialState)) { + self::$_initialState = get_class_vars('Router'); + self::_setPrefixes(); + return; + } + foreach (self::$_initialState as $key => $val) { + if ($key != '_initialState') { + self::${$key} = $val; + } + } + self::_setPrefixes(); + } + +/** + * Promote a route (by default, the last one added) to the beginning of the list + * + * @param integer $which A zero-based array index representing the route to move. For example, + * if 3 routes have been added, the last route would be 2. + * @return boolean Returns false if no route exists at the position specified by $which. + */ + public static function promote($which = null) { + if ($which === null) { + $which = count(self::$routes) - 1; + } + if (!isset(self::$routes[$which])) { + return false; + } + $route =& self::$routes[$which]; + unset(self::$routes[$which]); + array_unshift(self::$routes, $route); + return true; + } + +/** + * Finds URL for specified action. + * + * Returns an URL pointing to a combination of controller and action. Param + * $url can be: + * + * - Empty - the method will find address to actual controller/action. + * - '/' - the method will find base URL of application. + * - A combination of controller/action - the method will find url for it. + * + * There are a few 'special' parameters that can change the final URL string that is generated + * + * - `base` - Set to false to remove the base path from the generated url. If your application + * is not in the root directory, this can be used to generate urls that are 'cake relative'. + * cake relative urls are required when using requestAction. + * - `?` - Takes an array of query string parameters + * - `#` - Allows you to set url hash fragments. + * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated urls. + * + * @param string|array $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4" + * or an array specifying any of the following: 'controller', 'action', + * and/or 'plugin', in addition to named arguments (keyed array elements), + * and standard URL arguments (indexed array elements) + * @param bool|array $full If (bool) true, the full base URL will be prepended to the result. + * If an array accepts the following keys + * - escape - used when making urls embedded in html escapes query string '&' + * - full - if true the full base URL will be prepended. + * @return string Full translated URL with base path. + */ + public static function url($url = null, $full = false) { + $params = array('plugin' => null, 'controller' => null, 'action' => 'index'); + + if (is_bool($full)) { + $escape = false; + } else { + extract($full + array('escape' => false, 'full' => false)); + } + + $path = array('base' => null); + if (!empty(self::$_requests)) { + $request = self::$_requests[count(self::$_requests) - 1]; + $params = $request->params; + $path = array('base' => $request->base, 'here' => $request->here); + } + + $base = $path['base']; + $extension = $output = $q = $frag = null; + + if (empty($url)) { + $output = isset($path['here']) ? $path['here'] : '/'; + if ($full && defined('FULL_BASE_URL')) { + $output = FULL_BASE_URL . $output; + } + return $output; + } elseif (is_array($url)) { + if (isset($url['base']) && $url['base'] === false) { + $base = null; + unset($url['base']); + } + if (isset($url['full_base']) && $url['full_base'] === true) { + $full = true; + unset($url['full_base']); + } + if (isset($url['?'])) { + $q = $url['?']; + unset($url['?']); + } + if (isset($url['#'])) { + $frag = '#' . $url['#']; + unset($url['#']); + } + if (isset($url['ext'])) { + $extension = '.' . $url['ext']; + unset($url['ext']); + } + if (empty($url['action'])) { + if (empty($url['controller']) || $params['controller'] === $url['controller']) { + $url['action'] = $params['action']; + } else { + $url['action'] = 'index'; + } + } + + $prefixExists = (array_intersect_key($url, array_flip(self::$_prefixes))); + foreach (self::$_prefixes as $prefix) { + if (!empty($params[$prefix]) && !$prefixExists) { + $url[$prefix] = true; + } elseif (isset($url[$prefix]) && !$url[$prefix]) { + unset($url[$prefix]); + } + if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) { + $url['action'] = substr($url['action'], strlen($prefix) + 1); + } + } + + $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']); + + $match = false; + + for ($i = 0, $len = count(self::$routes); $i < $len; $i++) { + $originalUrl = $url; + + if (isset(self::$routes[$i]->options['persist'], $params)) { + $url = self::$routes[$i]->persistParams($url, $params); + } + + if ($match = self::$routes[$i]->match($url)) { + $output = trim($match, '/'); + break; + } + $url = $originalUrl; + } + if ($match === false) { + $output = self::_handleNoRoute($url); + } + } else { + if ( + (strpos($url, '://') !== false || + (strpos($url, 'javascript:') === 0) || + (strpos($url, 'mailto:') === 0)) || + (!strncmp($url, '#', 1)) + ) { + return $url; + } + if (substr($url, 0, 1) === '/') { + $output = substr($url, 1); + } else { + foreach (self::$_prefixes as $prefix) { + if (isset($params[$prefix])) { + $output .= $prefix . '/'; + break; + } + } + if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) { + $output .= Inflector::underscore($params['plugin']) . '/'; + } + $output .= Inflector::underscore($params['controller']) . '/' . $url; + } + } + $protocol = preg_match('#^[a-z][a-z0-9+-.]*\://#i', $output); + if ($protocol === 0) { + $output = str_replace('//', '/', $base . '/' . $output); + + if ($full && defined('FULL_BASE_URL')) { + $output = FULL_BASE_URL . $output; + } + if (!empty($extension)) { + $output = rtrim($output, '/'); + } + } + return $output . $extension . self::queryString($q, array(), $escape) . $frag; + } + +/** + * A special fallback method that handles url arrays that cannot match + * any defined routes. + * + * @param array $url A url that didn't match any routes + * @return string A generated url for the array + * @see Router::url() + */ + protected static function _handleNoRoute($url) { + $named = $args = array(); + $skip = array_merge( + array('bare', 'action', 'controller', 'plugin', 'prefix'), + self::$_prefixes + ); + + $keys = array_values(array_diff(array_keys($url), $skip)); + $count = count($keys); + + // Remove this once parsed URL parameters can be inserted into 'pass' + for ($i = 0; $i < $count; $i++) { + $key = $keys[$i]; + if (is_numeric($keys[$i])) { + $args[] = $url[$key]; + } else { + $named[$key] = $url[$key]; + } + } + + list($args, $named) = array(Hash::filter($args), Hash::filter($named)); + foreach (self::$_prefixes as $prefix) { + $prefixed = $prefix . '_'; + if (!empty($url[$prefix]) && strpos($url['action'], $prefixed) === 0) { + $url['action'] = substr($url['action'], strlen($prefixed) * -1); + break; + } + } + + if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) { + $url['action'] = null; + } + + $urlOut = array_filter(array($url['controller'], $url['action'])); + + if (isset($url['plugin'])) { + array_unshift($urlOut, $url['plugin']); + } + + foreach (self::$_prefixes as $prefix) { + if (isset($url[$prefix])) { + array_unshift($urlOut, $prefix); + break; + } + } + $output = implode('/', $urlOut); + + if (!empty($args)) { + $output .= '/' . implode('/', array_map('rawurlencode', $args)); + } + + if (!empty($named)) { + foreach ($named as $name => $value) { + if (is_array($value)) { + $flattend = Hash::flatten($value, ']['); + foreach ($flattend as $namedKey => $namedValue) { + $output .= '/' . $name . "[$namedKey]" . self::$_namedConfig['separator'] . rawurlencode($namedValue); + } + } else { + $output .= '/' . $name . self::$_namedConfig['separator'] . rawurlencode($value); + } + } + } + return $output; + } + +/** + * Generates a well-formed querystring from $q + * + * @param string|array $q Query string Either a string of already compiled query string arguments or + * an array of arguments to convert into a query string. + * @param array $extra Extra querystring parameters. + * @param boolean $escape Whether or not to use escaped & + * @return array + */ + public static function queryString($q, $extra = array(), $escape = false) { + if (empty($q) && empty($extra)) { + return null; + } + $join = '&'; + if ($escape === true) { + $join = '&'; + } + $out = ''; + + if (is_array($q)) { + $q = array_merge($q, $extra); + } else { + $out = $q; + $q = $extra; + } + $addition = http_build_query($q, null, $join); + + if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) != $join) { + $out .= $join; + } + + $out .= $addition; + + if (isset($out[0]) && $out[0] != '?') { + $out = '?' . $out; + } + return $out; + } + +/** + * Reverses a parsed parameter array into a string. Works similarly to Router::url(), but + * Since parsed URL's contain additional 'pass' and 'named' as well as 'url.url' keys. + * Those keys need to be specially handled in order to reverse a params array into a string url. + * + * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those + * are used for CakePHP internals and should not normally be part of an output url. + * + * @param CakeRequest|array $params The params array or CakeRequest object that needs to be reversed. + * @param boolean $full Set to true to include the full url including the protocol when reversing + * the url. + * @return string The string that is the reversed result of the array + */ + public static function reverse($params, $full = false) { + if ($params instanceof CakeRequest) { + $url = $params->query; + $params = $params->params; + } else { + $url = $params['url']; + } + $pass = isset($params['pass']) ? $params['pass'] : array(); + $named = isset($params['named']) ? $params['named'] : array(); + + unset( + $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'], + $params['autoRender'], $params['bare'], $params['requested'], $params['return'], + $params['_Token'] + ); + $params = array_merge($params, $pass, $named); + if (!empty($url)) { + $params['?'] = $url; + } + return Router::url($params, $full); + } + +/** + * Normalizes a URL for purposes of comparison. Will strip the base path off + * and replace any double /'s. It will not unify the casing and underscoring + * of the input value. + * + * @param array|string $url URL to normalize Either an array or a string url. + * @return string Normalized URL + */ + public static function normalize($url = '/') { + if (is_array($url)) { + $url = Router::url($url); + } + if (preg_match('/^[a-z\-]+:\/\//', $url)) { + return $url; + } + $request = Router::getRequest(); + + if (!empty($request->base) && stristr($url, $request->base)) { + $url = preg_replace('/^' . preg_quote($request->base, '/') . '/', '', $url, 1); + } + $url = '/' . $url; + + while (strpos($url, '//') !== false) { + $url = str_replace('//', '/', $url); + } + $url = preg_replace('/(?:(\/$))/', '', $url); + + if (empty($url)) { + return '/'; + } + return $url; + } + +/** + * Returns the route matching the current request URL. + * + * @return CakeRoute Matching route object. + */ + public static function &requestRoute() { + return self::$_currentRoute[0]; + } + +/** + * Returns the route matching the current request (useful for requestAction traces) + * + * @return CakeRoute Matching route object. + */ + public static function ¤tRoute() { + return self::$_currentRoute[count(self::$_currentRoute) - 1]; + } + +/** + * Removes the plugin name from the base URL. + * + * @param string $base Base URL + * @param string $plugin Plugin name + * @return string base url with plugin name removed if present + */ + public static function stripPlugin($base, $plugin = null) { + if ($plugin != null) { + $base = preg_replace('/(?:' . $plugin . ')/', '', $base); + $base = str_replace('//', '', $base); + $pos1 = strrpos($base, '/'); + $char = strlen($base) - 1; + + if ($pos1 === $char) { + $base = substr($base, 0, $char); + } + } + return $base; + } + +/** + * Instructs the router to parse out file extensions from the URL. For example, + * http://example.com/posts.rss would yield an file extension of "rss". + * The file extension itself is made available in the controller as + * `$this->params['ext']`, and is used by the RequestHandler component to + * automatically switch to alternate layouts and templates, and load helpers + * corresponding to the given content, i.e. RssHelper. Switching layouts and helpers + * requires that the chosen extension has a defined mime type in `CakeResponse` + * + * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml'); + * If no parameters are given, anything after the first . (dot) after the last / in the URL will be + * parsed, excluding querystring parameters (i.e. ?q=...). + * + * @return void + * @see RequestHandler::startup() + */ + public static function parseExtensions() { + self::$_parseExtensions = true; + if (func_num_args() > 0) { + self::setExtensions(func_get_args(), false); + } + } + +/** + * Get the list of extensions that can be parsed by Router. + * To initially set extensions use `Router::parseExtensions()` + * To add more see `setExtensions()` + * + * @return array Array of extensions Router is configured to parse. + */ + public static function extensions() { + return self::$_validExtensions; + } + +/** + * Set/add valid extensions. + * To have the extensions parsed you still need to call `Router::parseExtensions()` + * + * @param array $extensions List of extensions to be added as valid extension + * @param boolean $merge Default true will merge extensions. Set to false to override current extensions + * @return array + */ + public static function setExtensions($extensions, $merge = true) { + if (!is_array($extensions)) { + return self::$_validExtensions; + } + if (!$merge) { + return self::$_validExtensions = $extensions; + } + return self::$_validExtensions = array_merge(self::$_validExtensions, $extensions); + } + +} + +//Save the initial state +Router::reload(); -- cgit v1.2.3