Seditio Source
Root |
./othercms/croogo-4.0.7/vendor/cakephp/cakephp/src/Routing/RouteCollection.php
<?php
/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         3.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Routing;

use
Cake\Routing\Exception\DuplicateNamedRouteException;
use
Cake\Routing\Exception\MissingRouteException;
use
Cake\Routing\Route\Route;
use
Psr\Http\Message\ServerRequestInterface;
use
RuntimeException;

/**
 * Contains a collection of routes.
 *
 * Provides an interface for adding/removing routes
 * and parsing/generating URLs with the routes it contains.
 *
 * @internal
 */
class RouteCollection
{
   
/**
     * The routes connected to this collection.
     *
     * @var array
     */
   
protected $_routeTable = [];

   
/**
     * The routes connected to this collection.
     *
     * @var \Cake\Routing\Route\Route[]
     */
   
protected $_routes = [];

   
/**
     * The hash map of named routes that are in this collection.
     *
     * @var \Cake\Routing\Route\Route[]
     */
   
protected $_named = [];

   
/**
     * Routes indexed by path prefix.
     *
     * @var array
     */
   
protected $_paths = [];

   
/**
     * A map of middleware names and the related objects.
     *
     * @var array
     */
   
protected $_middleware = [];

   
/**
     * A map of middleware group names and the related middleware names.
     *
     * @var array
     */
   
protected $_middlewareGroups = [];

   
/**
     * A map of paths and the list of applicable middleware.
     *
     * @var array
     */
   
protected $_middlewarePaths = [];

   
/**
     * Route extensions
     *
     * @var string[]
     */
   
protected $_extensions = [];

   
/**
     * Add a route to the collection.
     *
     * @param \Cake\Routing\Route\Route $route The route object to add.
     * @param array $options Additional options for the route. Primarily for the
     *   `_name` option, which enables named routes.
     * @return void
     */
   
public function add(Route $route, array $options = [])
    {
       
$this->_routes[] = $route;

       
// Explicit names
       
if (isset($options['_name'])) {
            if (isset(
$this->_named[$options['_name']])) {
               
$matched = $this->_named[$options['_name']];
                throw new
DuplicateNamedRouteException([
                   
'name' => $options['_name'],
                   
'url' => $matched->template,
                   
'duplicate' => $matched,
                ]);
            }
           
$this->_named[$options['_name']] = $route;
        }

       
// Generated names.
       
$name = $route->getName();
        if (!isset(
$this->_routeTable[$name])) {
           
$this->_routeTable[$name] = [];
        }
       
$this->_routeTable[$name][] = $route;

       
// Index path prefixes (for parsing)
       
$path = $route->staticPath();
       
$this->_paths[$path][] = $route;

       
$extensions = $route->getExtensions();
        if (
count($extensions) > 0) {
           
$this->setExtensions($extensions);
        }
    }

   
/**
     * Takes the URL string and iterates the routes until one is able to parse the route.
     *
     * @param string $url URL to parse.
     * @param string $method The HTTP method to use.
     * @return array An array of request parameters parsed from the URL.
     * @throws \Cake\Routing\Exception\MissingRouteException When a URL has no matching route.
     */
   
public function parse($url, $method = '')
    {
       
$decoded = urldecode($url);

       
// Sort path segments matching longest paths first.
       
$paths = array_keys($this->_paths);
       
rsort($paths);

        foreach (
$paths as $path) {
            if (
strpos($decoded, $path) !== 0) {
                continue;
            }

           
$queryParameters = null;
            if (
strpos($url, '?') !== false) {
                list(
$url, $queryParameters) = explode('?', $url, 2);
               
parse_str($queryParameters, $queryParameters);
            }
           
/** @var \Cake\Routing\Route\Route $route */
           
foreach ($this->_paths[$path] as $route) {
               
$r = $route->parse($url, $method);
                if (
$r === false) {
                    continue;
                }
                if (
$queryParameters) {
                   
$r['?'] = $queryParameters;
                }

                return
$r;
            }
        }

       
$exceptionProperties = ['url' => $url];
        if (
$method !== '') {
           
// Ensure that if the method is included, it is the first element of
            // the array, to match the order that the strings are printed in the
            // MissingRouteException error message, $_messageTemplateWithMethod.
           
$exceptionProperties = array_merge(['method' => $method], $exceptionProperties);
        }
        throw new
MissingRouteException($exceptionProperties);
    }

   
/**
     * Takes the ServerRequestInterface, iterates the routes until one is able to parse the route.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request The request to parse route data from.
     * @return array An array of request parameters parsed from the URL.
     * @throws \Cake\Routing\Exception\MissingRouteException When a URL has no matching route.
     */
   
public function parseRequest(ServerRequestInterface $request)
    {
       
$uri = $request->getUri();
       
$urlPath = urldecode($uri->getPath());

       
// Sort path segments matching longest paths first.
       
$paths = array_keys($this->_paths);
       
rsort($paths);

        foreach (
$paths as $path) {
            if (
strpos($urlPath, $path) !== 0) {
                continue;
            }

           
/** @var \Cake\Routing\Route\Route $route */
           
foreach ($this->_paths[$path] as $route) {
               
$r = $route->parseRequest($request);
                if (
$r === false) {
                    continue;
                }
                if (
$uri->getQuery()) {
                   
parse_str($uri->getQuery(), $queryParameters);
                   
$r['?'] = $queryParameters;
                }

                return
$r;
            }
        }
        throw new
MissingRouteException(['url' => $urlPath]);
    }

   
/**
     * Get the set of names from the $url. Accepts both older style array urls,
     * and newer style urls containing '_name'
     *
     * @param array $url The url to match.
     * @return string[] The set of names of the url
     */
   
protected function _getNames($url)
    {
       
$plugin = false;
        if (isset(
$url['plugin']) && $url['plugin'] !== false) {
           
$plugin = strtolower($url['plugin']);
        }
       
$prefix = false;
        if (isset(
$url['prefix']) && $url['prefix'] !== false) {
           
$prefix = strtolower($url['prefix']);
        }
       
$controller = strtolower($url['controller']);
       
$action = strtolower($url['action']);

       
$names = [
           
"${controller}:${action}",
           
"${controller}:_action",
           
"_controller:${action}",
           
'_controller:_action',
        ];

       
// No prefix, no plugin
       
if ($prefix === false && $plugin === false) {
            return
$names;
        }

       
// Only a plugin
       
if ($prefix === false) {
            return [
               
"${plugin}.${controller}:${action}",
               
"${plugin}.${controller}:_action",
               
"${plugin}._controller:${action}",
               
"${plugin}._controller:_action",
               
"_plugin.${controller}:${action}",
               
"_plugin.${controller}:_action",
               
"_plugin._controller:${action}",
               
'_plugin._controller:_action',
            ];
        }

       
// Only a prefix
       
if ($plugin === false) {
            return [
               
"${prefix}:${controller}:${action}",
               
"${prefix}:${controller}:_action",
               
"${prefix}:_controller:${action}",
               
"${prefix}:_controller:_action",
               
"_prefix:${controller}:${action}",
               
"_prefix:${controller}:_action",
               
"_prefix:_controller:${action}",
               
'_prefix:_controller:_action',
            ];
        }

       
// Prefix and plugin has the most options
        // as there are 4 factors.
       
return [
           
"${prefix}:${plugin}.${controller}:${action}",
           
"${prefix}:${plugin}.${controller}:_action",
           
"${prefix}:${plugin}._controller:${action}",
           
"${prefix}:${plugin}._controller:_action",
           
"${prefix}:_plugin.${controller}:${action}",
           
"${prefix}:_plugin.${controller}:_action",
           
"${prefix}:_plugin._controller:${action}",
           
"${prefix}:_plugin._controller:_action",
           
"_prefix:${plugin}.${controller}:${action}",
           
"_prefix:${plugin}.${controller}:_action",
           
"_prefix:${plugin}._controller:${action}",
           
"_prefix:${plugin}._controller:_action",
           
"_prefix:_plugin.${controller}:${action}",
           
"_prefix:_plugin.${controller}:_action",
           
"_prefix:_plugin._controller:${action}",
           
'_prefix:_plugin._controller:_action',
        ];
    }

   
/**
     * Reverse route or match a $url array with the connected routes.
     *
     * Returns either the URL string generated by the route,
     * or throws an exception on failure.
     *
     * @param array $url The URL to match.
     * @param array $context The request context to use. Contains _base, _port,
     *    _host, _scheme and params keys.
     * @return string The URL string on match.
     * @throws \Cake\Routing\Exception\MissingRouteException When no route could be matched.
     */
   
public function match($url, $context)
    {
       
// Named routes support optimization.
       
if (isset($url['_name'])) {
           
$name = $url['_name'];
            unset(
$url['_name']);
            if (isset(
$this->_named[$name])) {
               
$route = $this->_named[$name];
               
$out = $route->match($url + $route->defaults, $context);
                if (
$out) {
                    return
$out;
                }
                throw new
MissingRouteException([
                   
'url' => $name,
                   
'context' => $context,
                   
'message' => 'A named route was found for "%s", but matching failed.',
                ]);
            }
            throw new
MissingRouteException(['url' => $name, 'context' => $context]);
        }

        foreach (
$this->_getNames($url) as $name) {
            if (empty(
$this->_routeTable[$name])) {
                continue;
            }
           
/** @var \Cake\Routing\Route\Route $route */
           
foreach ($this->_routeTable[$name] as $route) {
               
$match = $route->match($url, $context);
                if (
$match) {
                    return
strlen($match) > 1 ? trim($match, '/') : $match;
                }
            }
        }
        throw new
MissingRouteException(['url' => var_export($url, true), 'context' => $context]);
    }

   
/**
     * Get all the connected routes as a flat list.
     *
     * @return \Cake\Routing\Route\Route[]
     */
   
public function routes()
    {
        return
$this->_routes;
    }

   
/**
     * Get the connected named routes.
     *
     * @return \Cake\Routing\Route\Route[]
     */
   
public function named()
    {
        return
$this->_named;
    }

   
/**
     * Get/set the extensions that the route collection could handle.
     *
     * @param string[]|string|null $extensions Either the list of extensions to set,
     *   or null to get.
     * @param bool $merge Whether to merge with or override existing extensions.
     *   Defaults to `true`.
     * @return string[] The valid extensions.
     * @deprecated 3.5.0 Use getExtensions()/setExtensions() instead.
     */
   
public function extensions($extensions = null, $merge = true)
    {
       
deprecationWarning(
           
'RouteCollection::extensions() is deprecated. ' .
           
'Use RouteCollection::setExtensions()/getExtensions() instead.'
       
);
        if (
$extensions !== null) {
           
$this->setExtensions((array)$extensions, $merge);
        }

        return
$this->getExtensions();
    }

   
/**
     * Get the extensions that can be handled.
     *
     * @return string[] The valid extensions.
     */
   
public function getExtensions()
    {
        return
$this->_extensions;
    }

   
/**
     * Set the extensions that the route collection can handle.
     *
     * @param string[] $extensions The list of extensions to set.
     * @param bool $merge Whether to merge with or override existing extensions.
     *   Defaults to `true`.
     * @return $this
     */
   
public function setExtensions(array $extensions, $merge = true)
    {
        if (
$merge) {
           
$extensions = array_unique(array_merge(
               
$this->_extensions,
               
$extensions
           
));
        }
       
$this->_extensions = $extensions;

        return
$this;
    }

   
/**
     * Register a middleware with the RouteCollection.
     *
     * Once middleware has been registered, it can be applied to the current routing
     * scope or any child scopes that share the same RouteCollection.
     *
     * @param string $name The name of the middleware. Used when applying middleware to a scope.
     * @param callable|string $middleware The middleware callable or class name to register.
     * @return $this
     * @throws \RuntimeException
     */
   
public function registerMiddleware($name, $middleware)
    {
       
$this->_middleware[$name] = $middleware;

        return
$this;
    }

   
/**
     * Add middleware to a middleware group
     *
     * @param string $name Name of the middleware group
     * @param string[] $middlewareNames Names of the middleware
     * @return $this
     * @throws \RuntimeException
     */
   
public function middlewareGroup($name, array $middlewareNames)
    {
        if (
$this->hasMiddleware($name)) {
           
$message = "Cannot add middleware group '$name'. A middleware by this name has already been registered.";
            throw new
RuntimeException($message);
        }

        foreach (
$middlewareNames as $middlewareName) {
            if (!
$this->hasMiddleware($middlewareName)) {
               
$message = "Cannot add '$middlewareName' middleware to group '$name'. It has not been registered.";
                throw new
RuntimeException($message);
            }
        }

       
$this->_middlewareGroups[$name] = $middlewareNames;

        return
$this;
    }

   
/**
     * Check if the named middleware group has been created.
     *
     * @param string $name The name of the middleware group to check.
     * @return bool
     */
   
public function hasMiddlewareGroup($name)
    {
        return
array_key_exists($name, $this->_middlewareGroups);
    }

   
/**
     * Check if the named middleware has been registered.
     *
     * @param string $name The name of the middleware to check.
     * @return bool
     */
   
public function hasMiddleware($name)
    {
        return isset(
$this->_middleware[$name]);
    }

   
/**
     * Check if the named middleware or middleware group has been registered.
     *
     * @param string $name The name of the middleware to check.
     * @return bool
     */
   
public function middlewareExists($name)
    {
        return
$this->hasMiddleware($name) || $this->hasMiddlewareGroup($name);
    }

   
/**
     * Apply a registered middleware(s) for the provided path
     *
     * @param string $path The URL path to register middleware for.
     * @param string[] $middleware The middleware names to add for the path.
     * @return $this
     * @throws \RuntimeException
     */
   
public function applyMiddleware($path, array $middleware)
    {
        foreach (
$middleware as $name) {
            if (!
$this->hasMiddleware($name) && !$this->hasMiddlewareGroup($name)) {
               
$message = "Cannot apply '$name' middleware or middleware group to path '$path'. It has not been registered.";
                throw new
RuntimeException($message);
            }
        }
       
// Matches route element pattern in Cake\Routing\Route
       
$path = '#^' . preg_quote($path, '#') . '#';
       
$path = preg_replace('/\\\\:([a-z0-9-_]+(?<![-_]))/i', '[^/]+', $path);

        if (!isset(
$this->_middlewarePaths[$path])) {
           
$this->_middlewarePaths[$path] = [];
        }
       
$this->_middlewarePaths[$path] = array_merge($this->_middlewarePaths[$path], $middleware);

        return
$this;
    }

   
/**
     * Get an array of middleware given a list of names
     *
     * @param string[] $names The names of the middleware or groups to fetch
     * @return array An array of middleware. If any of the passed names are groups,
     *   the groups middleware will be flattened into the returned list.
     * @throws \RuntimeException when a requested middleware does not exist.
     */
   
public function getMiddleware(array $names)
    {
       
$out = [];
        foreach (
$names as $name) {
            if (
$this->hasMiddlewareGroup($name)) {
               
$out = array_merge($out, $this->getMiddleware($this->_middlewareGroups[$name]));
                continue;
            }
            if (!
$this->hasMiddleware($name)) {
               
$message = "The middleware named '$name' has not been registered. Use registerMiddleware() to define it.";
                throw new
RuntimeException($message);
            }
           
$out[] = $this->_middleware[$name];
        }

        return
$out;
    }
}