no message
This commit is contained in:
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "nette/routing",
|
||||
"description": "Nette Routing: two-ways URL conversion",
|
||||
"keywords": ["nette"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"nette/http": "^3.0",
|
||||
"nette/utils": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"tracy/tracy": "^2.3"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+326
@@ -0,0 +1,326 @@
|
||||
Nette Routing: two-ways URL conversion
|
||||
======================================
|
||||
|
||||
[](https://packagist.org/packages/nette/routing)
|
||||
[](https://travis-ci.org/nette/routing)
|
||||
[](https://coveralls.io/github/nette/routing?branch=master)
|
||||
|
||||
Routing is a two-way conversion between URL and presenter action. *Two-way* means that we can both determine what presenter URL links to, but also vice versa: generate URL for given action.
|
||||
|
||||
|
||||
SimpleRouter
|
||||
------------
|
||||
|
||||
Desired URL format is set by a *router*. The most plain implementation of router is SimpleRouter. It can be used when there's no need for a specific URL format, or when `mod_rewrite` (or alternatives) is not available.
|
||||
|
||||
Addresses look like this:
|
||||
|
||||
```
|
||||
http://example.com/index.php?presenter=Product&action=detail&id=123
|
||||
```
|
||||
|
||||
The first parameter of `SimpleRouter` constructor is an array of default parameters.
|
||||
|
||||
```php
|
||||
// defaults to controller 'Homepage' and action 'default'
|
||||
$router = new Nette\Routing\SimpleRouter(['controller' => 'homepage', 'action' => 'default']);
|
||||
```
|
||||
|
||||
The second constructor parameter is optional and is used to pass additional flags (only `Router::ONE_WAY` is supported).
|
||||
|
||||
|
||||
Route: for prettier URLs
|
||||
------------------------
|
||||
|
||||
Human-friendly URLs (also more cool & prettier) are easier to remember and do help SEO. Nette Framework keeps current trends in mind and fully meets developers' desires.
|
||||
|
||||
All requests must be handled by `index.php` file. This can be accomplished e.g. by using Apache module `mod_rewrite` or Nginx's try_files.
|
||||
|
||||
Class Route can create addresses in pretty much any format one can though of. Let's start with a simple example generating a pretty URL for action `Product:default` with `id = 123`:
|
||||
|
||||
```
|
||||
http://example.com/product/detail/123
|
||||
```
|
||||
|
||||
The following snippet creates a `Route` object, passing path mask as the first argument and specifying default action in the second argument.
|
||||
|
||||
```php
|
||||
// action defaults to controller Homepage and action default
|
||||
$route = new Route('<controller>/<action>[/<id>]', ['controller' => 'homepage', 'action' => 'default']);
|
||||
```
|
||||
|
||||
This route is usable by all controllers and actions. Accepts paths such as `/article/edit/10` or `/catalog/list`, because the `id` part is wrapped in square brackets, which marks it as optional.
|
||||
|
||||
Because other parameters (`controller` and `action`) do have default values (`Homepage` and `default`), they are optional too. If their value is the same as the default one, they are skipped while URL is generated. Link to `Product:default` generates only `http://example.com/product` and link to `Homepage:default` generates only `http://example.com/`.
|
||||
|
||||
|
||||
Path Mask
|
||||
---------
|
||||
|
||||
The simplest path mask consists only of a **static URL** and a target action.
|
||||
|
||||
```php
|
||||
$route = new Route('products', ...);
|
||||
```
|
||||
|
||||
Most real masks however contain some **parameters**. Parameters are enclosed in angle brackets (e.g. `<year>`) and are passed to target presenter.
|
||||
|
||||
```php
|
||||
$route = new Route('history/<year>', ...);
|
||||
```
|
||||
|
||||
Mask can also contain traditional GET arguments (query after a question mark). Neither validation expressions nor more complex structures are supported in this part of path mask, but you can set what key belongs to which variable:
|
||||
|
||||
``` php
|
||||
$route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>', ...);
|
||||
```
|
||||
|
||||
The parameters before a question mark are called *path parameters* and the parameters after a question mark are called *query parameters*.
|
||||
|
||||
|
||||
Mask can not only describe path relative to application document root (web root), but can as well contain path relative to server document root (starts with a single slash) or absolute path with domain (starts with a double slash).
|
||||
|
||||
```php
|
||||
// relative to application document root (www directory)
|
||||
$route = new Route('<presenter>/<action>', ...);
|
||||
|
||||
// relative to server document root
|
||||
$route = new Route('/<presenter>/<action>', ...);
|
||||
|
||||
// absolute path including hostname
|
||||
$route = new Route('//<subdomain>.example.com/<presenter>/<action>', ...);
|
||||
```
|
||||
|
||||
Absolute path mask may utilize the following variables:
|
||||
- `%tld%` = top level domain, e.g. `com` or `org`
|
||||
- `%domain%` = second level domain, e.g. `example.com`
|
||||
- `%basePath%`
|
||||
|
||||
```php
|
||||
$route = new Route('//www.%domain%/%basePath%/<presenter>/<action>', ...);
|
||||
```
|
||||
|
||||
|
||||
|
||||
Route Collection
|
||||
----------------
|
||||
|
||||
Because we usually define more than one route, we wrap them into a RouteList.
|
||||
|
||||
```php
|
||||
use Nette\Routing\RouteList;
|
||||
use Nette\Routing\Route;
|
||||
|
||||
$router = new RouteList();
|
||||
$router[] = new Route('rss.xml', ...);
|
||||
$router[] = new Route('article/<id>', ...);
|
||||
$router[] = new Route('<presenter>/<action>[/<id>]', ...);
|
||||
```
|
||||
|
||||
Unlike other frameworks, Nette does not require routes to be named.
|
||||
|
||||
It's important in which order are the routes defined as they are tried from top to bottom. The rule of thumb here is that routes are declared from the **most specific at the top** to the **most vague at the bottom**. Keep in mind that huge amount of routes can negatively effect application speed, mostly when generating links. It's worth to keep routes as simple as possible.
|
||||
|
||||
|
||||
|
||||
Default Values
|
||||
--------------
|
||||
|
||||
Each parameter may have defined a default value in the mask:
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
|
||||
```
|
||||
|
||||
Or utilizing an array:
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter>/<action>/<id>', [
|
||||
'presenter' => 'Homepage',
|
||||
'action' => 'default',
|
||||
'id' => null,
|
||||
]);
|
||||
|
||||
// equals to the following complex notation
|
||||
$route = new Route('<presenter>/<action>/<id>', [
|
||||
'presenter' => array(
|
||||
Route::VALUE => 'Homepage',
|
||||
),
|
||||
'action' => array(
|
||||
Route::VALUE => 'default',
|
||||
),
|
||||
'id' => array(
|
||||
Route::VALUE => null,
|
||||
),
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
|
||||
Validation Expressions
|
||||
----------------------
|
||||
|
||||
Each parameter may have defined a **regular expression** which it needs to match. This regular expression is checked both when matching and generating URL. For example let's set `id` to be only numerical, using `\d+` regexp:
|
||||
|
||||
```php
|
||||
// regexp can be defined directly in the path mask after parameter name
|
||||
$route = new Route('<presenter>/<action>[/<id \d+>]', ...);
|
||||
|
||||
// equals to the following complex notation
|
||||
$route = new Route('<presenter>/<action>[/<id>]', [
|
||||
'presenter' => 'Homepage',
|
||||
'action' => 'default',
|
||||
'id' => [
|
||||
Route::PATTERN => '\d+',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
Default validation expression for path parameters is `[^/]+`, meaning all characters but a slash. If a parameter is supposed to match a slash as well, we can set the regular expression to `.+`.
|
||||
|
||||
Regular expressions are *case-sensitive* by default.
|
||||
|
||||
|
||||
Optional Sequences
|
||||
------------------
|
||||
|
||||
Square brackets denote optional parts of mask. Any part of mask may be set as optional, including those containing parameters:
|
||||
|
||||
```php
|
||||
$route = new Route('[<lang [a-z]{2}>/]<name>', ...);
|
||||
|
||||
// Accepted URLs: Parameters:
|
||||
// /en/download lang => en, name => download
|
||||
// /download lang => null, name => download
|
||||
```
|
||||
|
||||
Obviously, if a parameter is inside an optional sequence, it's optional too and defaults to `null`. Sequence should define it's surroundings, in this case a slash which must follow a parameter, if set.
|
||||
The technique may be used for example for optional language subdomains:
|
||||
|
||||
```php
|
||||
$route = new Route('//[<lang=en>.]%domain%/<presenter>/<action>', ...);
|
||||
```
|
||||
|
||||
Sequences may be freely nested and combined:
|
||||
|
||||
```php
|
||||
$route = new Route(
|
||||
'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
|
||||
...
|
||||
);
|
||||
|
||||
// Accepted URLs:
|
||||
// /cs/hello
|
||||
// /en-us/hello
|
||||
// /hello
|
||||
// /hello/page-12
|
||||
```
|
||||
|
||||
|
||||
URL generator tries to keep the URL as short as possible (while unique), so what can be omitted is not used. That's why `index[.html]` route generates `/index`. This behavior can be inverted by writing an exclamation mark after the leftmost square bracket that denotes the respective optional sequence:
|
||||
|
||||
```php
|
||||
// accepts both /hello and /hello.html, generates /hello
|
||||
$route = new Route('<name>[.html]');
|
||||
|
||||
// accepts both /hello and /hello.html, generates /hello.html
|
||||
$route = new Route('<name>[!.html]');
|
||||
```
|
||||
|
||||
|
||||
Optional parameters (ie. parameters having default value) without square brackets do behave as if wrapped like this:
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
|
||||
|
||||
// equals to:
|
||||
$route = new Route('[<presenter=Homepage>/[<action=default>/[<id>]]]');
|
||||
```
|
||||
|
||||
If we would like to change how the rightmost slashes are generated, that is instead of `/homepage/` get a `/homepage`, we can adjust the route:
|
||||
|
||||
```php
|
||||
$route = new Route('[<presenter=Homepage>[/<action=default>[/<id>]]]');
|
||||
```
|
||||
|
||||
|
||||
Filters and Translation
|
||||
-----------------------
|
||||
|
||||
It's a good practice to write source code in English, but what if you need your application to run in a different environment? Simple routes such as:
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter>/<action>/<id>', [
|
||||
'presenter' => 'Homepage',
|
||||
'action' => 'default',
|
||||
'id' => null,
|
||||
]);
|
||||
```
|
||||
|
||||
will generate English URLs, such as `/product/detail/123`, `/cart` or `/catalog/view`. If we would like to translate those URLs, we can use a *dictionary* defined under `Route::FILTER_TABLE` key. We'd extend the route so:
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter>/<action>/<id>', [
|
||||
'presenter' => [
|
||||
Route::VALUE => 'Homepage', // default value
|
||||
Route::FILTER_TABLE => [
|
||||
// translated string in URL => presenter
|
||||
'produkt' => 'Product',
|
||||
'einkaufswagen' => 'Cart',
|
||||
'katalog' => 'Catalog',
|
||||
],
|
||||
],
|
||||
'action' => [
|
||||
Route::VALUE => 'default',
|
||||
Route::FILTER_TABLE => [
|
||||
'sehen' => 'view',
|
||||
],
|
||||
],
|
||||
'id' => null,
|
||||
]);
|
||||
```
|
||||
|
||||
Multiple keys under `Route::FILTER_TABLE` may have the same value. That's how aliases are created. The last value is the canonical one (used for generating links).
|
||||
|
||||
Dictionaries may be applied to any path parameter. If a translation is not found, the original (non-translated) value is used. The route by default accepts both translated (e.g. `/einkaufswagen`) and original (e.g. `/cart`) URLs. If you would like to accept only translated URLs, you need to add `Route::FILTER_STRICT => true` to the route definition.
|
||||
|
||||
|
||||
Besides setting dictionaries as arrays, it's possible to set **input and output filters**. Input and output filter are callbacks defined under `Route::FILTER_IN` and `Route::FILTER_OUT` keys respectively.
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter=Homepage>/<action=default>', [
|
||||
'action' => [
|
||||
Route::FILTER_IN => function ($action) {
|
||||
return strrev($action);
|
||||
},
|
||||
Route::FILTER_OUT => function ($action) {
|
||||
return strrev($action);
|
||||
},
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
The **input filter** accepts value from URL and should return a value which will be passed to a presenter. The **output filter** accepts value from presenter and should return a value which will be used in URL. If any of those filters is unable to filter the value (usually because it is invalid), it should return `null`.
|
||||
|
||||
|
||||
Besides filters for specific parameters, you can also define **global filters** which accepts an associative array with all parameters and returns an array with filtered parameters. Global filters are defined under `null` key.
|
||||
|
||||
```php
|
||||
$route = new Route('<presenter>/<action>', [
|
||||
'presenter' => 'Homepage',
|
||||
'action' => 'default',
|
||||
null => [
|
||||
Route::FILTER_IN => function (array $params) {
|
||||
// ...
|
||||
return $params;
|
||||
},
|
||||
Route::FILTER_OUT => function (array $params) {
|
||||
// ...
|
||||
return $params;
|
||||
},
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
You can use *global filters* to filter certain parameter based on a value of another parameter, e.g. translate `<presenter>` and `<action>` based on `<lang>`.
|
||||
+576
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user