no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+576
View File
@@ -0,0 +1,576 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Routing;
use Nette;
use Nette\Utils\Strings;
/**
* The bidirectional route is responsible for mapping
* HTTP request to an array for dispatch and vice-versa.
*/
class Route implements Router
{
use Nette\SmartObject;
/** key used in metadata {@link Route::__construct} */
public const
VALUE = 'value',
PATTERN = 'pattern',
FILTER_IN = 'filterIn',
FILTER_OUT = 'filterOut',
FILTER_TABLE = 'filterTable',
FILTER_STRICT = 'filterStrict';
/** key used in metadata */
private const
DEFAULT = 'defOut',
FIXITY = 'fixity',
FILTER_TABLE_OUT = 'filterTO';
/** url type */
private const
HOST = 1,
PATH = 2,
RELATIVE = 3;
/** fixity types - how to handle default value? {@link Route::$metadata} */
private const
OPTIONAL = 0,
PATH_OPTIONAL = 1,
CONSTANT = 2;
/** @var array */
protected $defaultMeta = [
'#' => [ // default style for path parameters
self::PATTERN => '[^/]+',
self::FILTER_OUT => [__CLASS__, 'param2path'],
],
];
/** @var string */
private $mask;
/** @var array */
private $sequence;
/** @var string regular expression pattern */
private $re;
/** @var string[] parameter aliases in regular expression */
private $aliases;
/** @var array of [value & fixity, filterIn, filterOut] */
private $metadata = [];
/** @var array */
private $xlat;
/** @var int HOST, PATH, RELATIVE */
private $type;
/** @var string http | https */
private $scheme;
/**
* @param string $mask e.g. '<presenter>/<action>/<id \d{1,3}>'
*/
public function __construct(string $mask, array $metadata = [])
{
$this->setMask($mask, $metadata);
}
/**
* Maps HTTP request to an array.
*/
public function match(Nette\Http\IRequest $httpRequest): ?array
{
// combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
// 1) URL MASK
$url = $httpRequest->getUrl();
$re = $this->re;
if ($this->type === self::HOST) {
$host = $url->getHost();
$path = '//' . $host . $url->getPath();
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
$re = strtr($re, [
'/%basePath%/' => preg_quote($url->getBasePath(), '#'),
'%tld%' => preg_quote($parts[0], '#'),
'%domain%' => preg_quote(isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0], '#'),
'%sld%' => preg_quote($parts[1] ?? '', '#'),
'%host%' => preg_quote($host, '#'),
]);
} elseif ($this->type === self::RELATIVE) {
$basePath = $url->getBasePath();
if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
return null;
}
$path = substr($url->getPath(), strlen($basePath));
} else {
$path = $url->getPath();
}
if ($path !== '') {
$path = rtrim(rawurldecode($path), '/') . '/';
}
if (!$matches = Strings::match($path, $re)) {
// stop, not matched
return null;
}
// assigns matched values to parameters
$params = [];
foreach ($matches as $k => $v) {
if (is_string($k) && $v !== '') {
$params[$this->aliases[$k]] = $v;
}
}
// 2) CONSTANT FIXITY
foreach ($this->metadata as $name => $meta) {
if (!isset($params[$name]) && isset($meta[self::FIXITY]) && $meta[self::FIXITY] !== self::OPTIONAL) {
$params[$name] = null; // cannot be overwriten in 3) and detected by isset() in 4)
}
}
// 3) QUERY
if ($this->xlat) {
$params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
} else {
$params += $httpRequest->getQuery();
}
// 4) APPLY FILTERS & FIXITY
foreach ($this->metadata as $name => $meta) {
if (isset($params[$name])) {
if (!is_scalar($params[$name])) {
// do nothing
} elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters
$params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
} elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) {
return null; // rejected by filterTable
} elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters
$params[$name] = $meta[self::FILTER_IN]((string) $params[$name]);
if ($params[$name] === null && !isset($meta[self::FIXITY])) {
return null; // rejected by filter
}
}
} elseif (isset($meta[self::FIXITY])) {
$params[$name] = $meta[self::VALUE];
}
}
if (isset($this->metadata[null][self::FILTER_IN])) {
$params = $this->metadata[null][self::FILTER_IN]($params);
if ($params === null) {
return null;
}
}
return $params;
}
/**
* Constructs absolute URL from array.
*/
public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string
{
$metadata = $this->metadata;
if (isset($metadata[null][self::FILTER_OUT])) {
$params = $metadata[null][self::FILTER_OUT]($params);
if ($params === null) {
return null;
}
}
foreach ($metadata as $name => $meta) {
if (!isset($params[$name])) {
continue; // retains null values
}
if (is_scalar($params[$name])) {
$params[$name] = $params[$name] === false ? '0' : (string) $params[$name];
}
if (isset($meta[self::FIXITY])) {
if ($params[$name] === $meta[self::VALUE]) { // remove default values; null values are retain
unset($params[$name]);
continue;
} elseif ($meta[self::FIXITY] === self::CONSTANT) {
return null; // missing or wrong parameter '$name'
}
}
if (is_scalar($params[$name]) && isset($meta[self::FILTER_TABLE_OUT][$params[$name]])) {
$params[$name] = $meta[self::FILTER_TABLE_OUT][$params[$name]];
} elseif (isset($meta[self::FILTER_TABLE_OUT]) && !empty($meta[self::FILTER_STRICT])) {
return null;
} elseif (isset($meta[self::FILTER_OUT])) {
$params[$name] = $meta[self::FILTER_OUT]($params[$name]);
}
if (isset($meta[self::PATTERN]) && !preg_match("#(?:{$meta[self::PATTERN]})\\z#A", rawurldecode((string) $params[$name]))) {
return null; // pattern not match
}
}
// compositing path
$sequence = $this->sequence;
$brackets = [];
$required = null; // null for auto-optional
$url = '';
$i = count($sequence) - 1;
do {
$url = $sequence[$i] . $url;
if ($i === 0) {
break;
}
$i--;
$name = $sequence[$i--]; // parameter name
if ($name === ']') { // opening optional part
$brackets[] = $url;
} elseif ($name[0] === '[') { // closing optional part
$tmp = array_pop($brackets);
if ($required < count($brackets) + 1) { // is this level optional?
if ($name !== '[!') { // and not "required"-optional
$url = $tmp;
}
} else {
$required = count($brackets);
}
} elseif ($name[0] === '?') { // "foo" parameter
continue;
} elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
$required = count($brackets); // make this level required
$url = $params[$name] . $url;
unset($params[$name]);
} elseif (isset($metadata[$name][self::FIXITY])) { // has default value?
if ($required === null && !$brackets) { // auto-optional
$url = '';
} else {
$url = $metadata[$name][self::DEFAULT] . $url;
}
} else {
return null; // missing parameter '$name'
}
} while (true);
$scheme = $this->scheme ?: $refUrl->getScheme();
// absolutize path
if ($this->type === self::RELATIVE) {
$url = (($tmp = $refUrl->getAuthority()) ? "//$tmp" : '') . $refUrl->getBasePath() . $url;
} elseif ($this->type === self::PATH) {
$url = (($tmp = $refUrl->getAuthority()) ? "//$tmp" : '') . $url;
} else {
$host = $refUrl->getHost();
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
$url = strtr($url, [
'/%basePath%/' => $refUrl->getBasePath(),
'%tld%' => $parts[0],
'%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0],
'%sld%' => $parts[1] ?? '',
'%host%' => $host,
]);
}
$url = $scheme . ':' . $url;
// build query string
if ($this->xlat) {
$params = self::renameKeys($params, $this->xlat);
}
$sep = ini_get('arg_separator.input');
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
if ($query != '') { // intentionally ==
$url .= '?' . $query;
}
return $url;
}
/**
* Parse mask and array of default values; initializes object.
*/
private function setMask(string $mask, array $metadata): void
{
$this->mask = $mask;
// detect '//host/path' vs. '/abs. path' vs. 'relative path'
if (preg_match('#(?:(https?):)?(//.*)#A', $mask, $m)) {
$this->type = self::HOST;
[, $this->scheme, $mask] = $m;
} elseif (substr($mask, 0, 1) === '/') {
$this->type = self::PATH;
} else {
$this->type = self::RELATIVE;
}
foreach ($metadata as $name => $meta) {
if (!is_array($meta)) {
$metadata[$name] = $meta = [self::VALUE => $meta];
}
if (array_key_exists(self::VALUE, $meta)) {
if (is_scalar($meta[self::VALUE])) {
$metadata[$name][self::VALUE] = $meta[self::VALUE] === false ? '0' : (string) $meta[self::VALUE];
}
$metadata[$name]['fixity'] = self::CONSTANT;
}
}
if (strpbrk($mask, '?<>[]') === false) {
$this->re = '#' . preg_quote($mask, '#') . '/?\z#A';
$this->sequence = [$mask];
$this->metadata = $metadata;
return;
}
// PARSE MASK
// <parameter-name[=default] [pattern]> or [ or ] or ?...
$parts = Strings::split($mask, '/<([^<>= ]+)(=[^<> ]*)? *([^<>]*)>|(\[!?|\]|\s*\?.*)/');
$this->xlat = [];
$i = count($parts) - 1;
// PARSE QUERY PART OF MASK
if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
// name=<parameter-name [pattern]>
$matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^> ]+) *([^>]*)>/');
foreach ($matches as [, $param, $name, $pattern]) { // $pattern is not used
$meta = ($metadata[$name] ?? []) + ($this->defaultMeta['?' . $name] ?? []);
if (array_key_exists(self::VALUE, $meta)) {
$meta[self::FIXITY] = self::OPTIONAL;
}
unset($meta[self::PATTERN]);
$meta[self::FILTER_TABLE_OUT] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
$metadata[$name] = $meta;
if ($param !== '') {
$this->xlat[$name] = $param;
}
}
$i -= 5;
}
// PARSE PATH PART OF MASK
$brackets = 0; // optional level
$re = '';
$sequence = [];
$autoOptional = true;
$aliases = [];
do {
$part = $parts[$i]; // part of path
if (strpbrk($part, '<>') !== false) {
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
}
array_unshift($sequence, $part);
$re = preg_quote($part, '#') . $re;
if ($i === 0) {
break;
}
$i--;
$part = $parts[$i]; // [ or ]
if ($part === '[' || $part === ']' || $part === '[!') {
$brackets += $part[0] === '[' ? -1 : 1;
if ($brackets < 0) {
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
}
array_unshift($sequence, $part);
$re = ($part[0] === '[' ? '(?:' : ')?') . $re;
$i -= 4;
continue;
}
$pattern = trim($parts[$i--]); // validation condition (as regexp)
$default = $parts[$i--]; // default value
$name = $parts[$i--]; // parameter name
array_unshift($sequence, $name);
if ($name[0] === '?') { // "foo" parameter
$name = substr($name, 1);
$re = $pattern ? '(?:' . preg_quote($name, '#') . "|$pattern)$re" : preg_quote($name, '#') . $re;
$sequence[1] = $name . $sequence[1];
continue;
}
// pattern, condition & metadata
$meta = ($metadata[$name] ?? []) + ($this->defaultMeta[$name] ?? $this->defaultMeta['#']);
if ($pattern == '' && isset($meta[self::PATTERN])) {
$pattern = $meta[self::PATTERN];
}
if ($default !== '') {
$meta[self::VALUE] = substr($default, 1);
$meta[self::FIXITY] = self::PATH_OPTIONAL;
}
$meta[self::FILTER_TABLE_OUT] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
if (array_key_exists(self::VALUE, $meta)) {
if (isset($meta[self::FILTER_TABLE_OUT][$meta[self::VALUE]])) {
$meta[self::DEFAULT] = $meta[self::FILTER_TABLE_OUT][$meta[self::VALUE]];
} elseif (isset($meta[self::VALUE], $meta[self::FILTER_OUT])) {
$meta[self::DEFAULT] = $meta[self::FILTER_OUT]($meta[self::VALUE]);
} else {
$meta[self::DEFAULT] = $meta[self::VALUE];
}
}
$meta[self::PATTERN] = $pattern;
// include in expression
$aliases['p' . $i] = $name;
$re = '(?P<p' . $i . '>(?U)' . $pattern . ')' . $re;
if ($brackets) { // is in brackets?
if (!isset($meta[self::VALUE])) {
$meta[self::VALUE] = $meta[self::DEFAULT] = null;
}
$meta[self::FIXITY] = self::PATH_OPTIONAL;
} elseif (isset($meta[self::FIXITY])) {
if ($autoOptional) {
$re = '(?:' . $re . ')?';
}
$meta[self::FIXITY] = self::PATH_OPTIONAL;
} else {
$autoOptional = false;
}
$metadata[$name] = $meta;
} while (true);
if ($brackets) {
throw new Nette\InvalidArgumentException("Missing '[' in mask '$mask'.");
}
$this->aliases = $aliases;
$this->re = '#' . $re . '/?\z#A';
$this->metadata = $metadata;
$this->sequence = $sequence;
}
/**
* Returns mask.
*/
public function getMask(): string
{
return $this->mask;
}
/**
* Returns default values.
*/
public function getDefaults(): array
{
$defaults = [];
foreach ($this->metadata as $name => $meta) {
if (isset($meta[self::FIXITY])) {
$defaults[$name] = $meta[self::VALUE];
}
}
return $defaults;
}
/** @internal */
protected function getMetadata(): array
{
return $this->metadata;
}
/** @internal */
public function getConstantParameters(): array
{
$res = [];
foreach ($this->metadata as $name => $meta) {
if (isset($meta[self::FIXITY]) && $meta[self::FIXITY] === self::CONSTANT) {
$res[$name] = $meta[self::VALUE];
}
}
return $res;
}
/********************* Utilities ****************d*g**/
/**
* Rename keys in array.
*/
private static function renameKeys(array $arr, array $xlat): array
{
if (empty($xlat)) {
return $arr;
}
$res = [];
$occupied = array_flip($xlat);
foreach ($arr as $k => $v) {
if (isset($xlat[$k])) {
$res[$xlat[$k]] = $v;
} elseif (!isset($occupied[$k])) {
$res[$k] = $v;
}
}
return $res;
}
/**
* Url encode.
*/
public static function param2path(string $s): string
{
return str_replace('%2F', '/', rawurlencode($s));
}
}
+275
View File
@@ -0,0 +1,275 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Routing;
use Nette;
/**
* The router broker.
*/
class RouteList implements Router
{
use Nette\SmartObject;
/** @var self|null */
protected $parent;
/** @var array of [Router, flags] */
private $list = [];
/** @var Router[][]|null */
private $ranks;
/** @var string */
private $cacheKey;
/** @var string|null */
private $domain;
/** @var string|null */
private $path;
/** @var \SplObjectStorage|null */
private $refUrlCache;
public function __construct()
{
}
/**
* Maps HTTP request to an array.
*/
public function match(Nette\Http\IRequest $httpRequest): ?array
{
if ($this->domain) {
$host = $httpRequest->getUrl()->getHost();
if ($host !== $this->expandDomain($host)) {
return null;
}
}
if ($this->path) {
$url = $httpRequest->getUrl();
if (strncmp($url->getRelativePath(), $this->path, strlen($this->path))) {
return null;
}
$url = $url->withPath($url->getPath(), $url->getBasePath() . $this->path);
$httpRequest = $httpRequest->withUrl($url);
}
foreach ($this->list as [$router]) {
$params = $router->match($httpRequest);
if ($params !== null) {
return $params;
}
}
return null;
}
/**
* Constructs absolute URL from array.
*/
public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string
{
if ($this->domain) {
if (!isset($this->refUrlCache[$refUrl])) {
$this->refUrlCache[$refUrl] = $refUrl->withHost(
$this->expandDomain($refUrl->getHost())
);
}
$refUrl = $this->refUrlCache[$refUrl];
}
if ($this->path) {
if (!isset($this->refUrlCache[$refUrl])) {
$this->refUrlCache[$refUrl] = $refUrl->withPath($refUrl->getBasePath() . $this->path);
}
$refUrl = $this->refUrlCache[$refUrl];
}
if ($this->ranks === null) {
$this->warmupCache();
}
$key = $params[$this->cacheKey] ?? null;
if (!is_scalar($key) || !isset($this->ranks[$key])) {
$key = '*';
}
foreach ($this->ranks[$key] as $router) {
$url = $router->constructUrl($params, $refUrl);
if ($url !== null) {
return $url;
}
}
return null;
}
public function warmupCache(): void
{
// find best key
$candidates = [];
$routers = [];
foreach ($this->list as [$router, $flags]) {
if ($flags & self::ONE_WAY) {
continue;
} elseif ($router instanceof self) {
$router->warmupCache();
}
$params = $router instanceof Route
? $router->getConstantParameters()
: [];
foreach (array_filter($params, 'is_scalar') as $name => $value) {
$candidates[$name][$value] = true;
}
$routers[] = [$router, $params];
}
$this->cacheKey = $count = null;
foreach ($candidates as $name => $items) {
if (count($items) > $count) {
$count = count($items);
$this->cacheKey = $name;
}
}
// classify routers
$ranks = ['*' => []];
foreach ($routers as [$router, $params]) {
$value = $params[$this->cacheKey] ?? null;
$values = $value === null
? array_keys($ranks)
: [is_scalar($value) ? $value : '*'];
foreach ($values as $value) {
if (!isset($ranks[$value])) {
$ranks[$value] = $ranks['*'];
}
$ranks[$value][] = $router;
}
}
$this->ranks = $ranks;
}
/**
* Adds a router.
* @return static
*/
public function add(Router $router, int $flags = 0)
{
$this->list[] = [$router, $flags];
$this->ranks = null;
return $this;
}
/**
* Prepends a router.
*/
public function prepend(Router $router, int $flags = 0): void
{
array_splice($this->list, 0, 0, [[$router, $flags]]);
$this->ranks = null;
}
/** @internal */
protected function modify(int $index, ?Router $router): void
{
if (!isset($this->list[$index])) {
throw new Nette\OutOfRangeException('Offset invalid or out of range');
} elseif ($router) {
$this->list[$index] = [$router, 0];
} else {
array_splice($this->list, $index, 1);
}
$this->ranks = null;
}
/**
* @param string $mask e.g. '<presenter>/<action>/<id \d{1,3}>'
* @param array $metadata default values or metadata
* @return static
*/
public function addRoute(string $mask, $metadata = [], int $flags = 0)
{
$this->add(new Route($mask, $metadata), $flags);
return $this;
}
/**
* Returns an iterator over all routers.
* @return static
*/
public function withDomain(string $domain)
{
$router = new static;
$router->domain = $domain;
$router->refUrlCache = new \SplObjectStorage;
$router->parent = $this;
$this->add($router);
return $router;
}
/**
* @return static
*/
public function withPath(string $path)
{
$router = new static;
$router->path = rtrim($path, '/') . '/';
$router->refUrlCache = new \SplObjectStorage;
$router->parent = $this;
$this->add($router);
return $router;
}
/**
* @return static
*/
public function end()
{
return $this->parent;
}
/**
* @return Router[]
*/
public function getRouters(): array
{
return array_column($this->list, 0);
}
private function expandDomain(string $host): string
{
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
return strtr($this->domain, [
'%tld%' => $parts[0],
'%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0],
'%sld%' => $parts[1] ?? '',
]);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Routing;
use Nette;
/**
* The bi-directional router.
*/
interface Router
{
/** for back compatibility */
public const ONE_WAY = 0b0001;
/**
* Maps HTTP request to an array.
*/
function match(Nette\Http\IRequest $httpRequest): ?array;
/**
* Constructs absolute URL from array.
*/
function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string;
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Routing;
use Nette;
/**
* The bidirectional route for trivial routing via query parameters.
*/
class SimpleRouter implements Router
{
use Nette\SmartObject;
/** @var array */
private $defaults;
public function __construct(array $defaults = [])
{
$this->defaults = $defaults;
}
/**
* Maps HTTP request to an array.
*/
public function match(Nette\Http\IRequest $httpRequest): ?array
{
return $httpRequest->getUrl()->getPathInfo() === ''
? $httpRequest->getQuery() + $this->defaults
: null;
}
/**
* Constructs absolute URL from array.
*/
public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string
{
// remove default values; null values are retain
foreach ($this->defaults as $key => $value) {
if (isset($params[$key]) && $params[$key] == $value) { // intentionally ==
unset($params[$key]);
}
}
$url = $refUrl->getHostUrl() . $refUrl->getPath();
$sep = ini_get('arg_separator.input');
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
if ($query != '') { // intentionally ==
$url .= '?' . $query;
}
return $url;
}
/**
* Returns default values.
*/
public function getDefaults(): array
{
return $this->defaults;
}
}