├── .github
└── workflows
│ └── php-qa.yml
├── LICENSE
├── composer.json
├── phpunit.xml.dist
└── src
├── Cortex.php
├── Cortex
├── Controller
│ ├── ControllerInterface.php
│ ├── QueryVarsController.php
│ └── RedirectController.php
├── Group
│ ├── Group.php
│ ├── GroupCollection.php
│ ├── GroupCollectionInterface.php
│ └── GroupInterface.php
├── Route
│ ├── ActionRoute.php
│ ├── DerivativeRouteTrait.php
│ ├── PriorityRouteCollection.php
│ ├── QueryRoute.php
│ ├── RedirectRoute.php
│ ├── Route.php
│ ├── RouteCollectionInterface.php
│ └── RouteInterface.php
├── Router
│ ├── MatchingResult.php
│ ├── ResultHandler.php
│ ├── ResultHandlerInterface.php
│ ├── RouteFilterIterator.php
│ ├── Router.php
│ └── RouterInterface.php
└── Uri
│ ├── PsrUri.php
│ ├── UriInterface.php
│ └── WordPressUri.php
└── Routes.php
/.github/workflows/php-qa.yml:
--------------------------------------------------------------------------------
1 | name: PHP Quality Assurance
2 | on:
3 | push:
4 | # Allow manually triggering the workflow.
5 | workflow_dispatch:
6 |
7 | # Cancels all previous workflow runs for the same branch that have not yet completed.
8 | concurrency:
9 | # The concurrency group contains the workflow name and the branch name.
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | qa:
15 | runs-on: ubuntu-latest
16 | if: "!contains(github.event.head_commit.message, 'ci skip')"
17 | strategy:
18 | fail-fast: true
19 | matrix:
20 | php-versions: ['5.5', '5.6']
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 |
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: ${{ matrix.php-versions }}
29 | ini-values: zend.assertions=1, error_reporting=-1, display_errors=On
30 | coverage: none
31 | tools: parallel-lint
32 | env:
33 | fail-fast: true
34 |
35 | - name: Check syntax error in sources
36 | run: parallel-lint ./src/ ./tests/
37 |
38 | - name: Install dependencies
39 | uses: "ramsey/composer-install@v2"
40 | with:
41 | # Bust the cache at least once a month - output format: YYYY-MM.
42 | custom-cache-suffix: $(date -u "+%Y-%m")
43 |
44 | - name: Check cross-version PHP compatibility
45 | if: ${{ matrix.php-versions == '5.6' }}
46 | run: composer phpcompat
47 |
48 | - name: Run unit tests
49 | run: ./vendor/bin/phpunit
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Giuseppe Mazzapica
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6 | associated documentation files (the "Software"), to deal in the Software without restriction,
7 | including without limitation the rights to use, copy, modify, merge, publish, distribute,
8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial
12 | portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
17 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "brain/cortex",
3 | "description": "Cortex is a package that implements a routing system in WordPress.",
4 | "keywords": [
5 | "wordpress",
6 | "routing",
7 | "fast route",
8 | "router",
9 | "rewrite rules",
10 | "pretty permalink"
11 | ],
12 | "homepage": "https://github.com/Brain-WP/Cortex",
13 | "license": "MIT",
14 | "authors": [
15 | {
16 | "name": "Giuseppe Mazzapica",
17 | "email": "giuseppe.mazzapica@gmail.com",
18 | "homepage": "http://gm.zoomlab.it",
19 | "role": "Developer"
20 | }
21 | ],
22 | "support": {
23 | "issues": "https://github.com/Brain-WP/Cortex/issues",
24 | "source": "https://github.com/Brain-WP/Cortex"
25 | },
26 | "require": {
27 | "php": ">=5.5",
28 | "nikic/fast-route": "~0.7.0",
29 | "psr/http-message": "<1.1"
30 | },
31 | "require-dev": {
32 | "phpunit/phpunit": "4.8.*",
33 | "mockery/mockery": "0.9.3",
34 | "brain/monkey": "~1.2.0",
35 | "gmazzap/andrew": "~1.0.0",
36 | "squizlabs/php_codesniffer": "^3.7",
37 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
38 | "phpcompatibility/php-compatibility": "^9.3"
39 | },
40 | "autoload": {
41 | "psr-4": {
42 | "Brain\\": "src/"
43 | }
44 | },
45 | "autoload-dev": {
46 | "psr-4": {
47 | "Brain\\Cortex\\Tests\\": "tests/src/"
48 | }
49 | },
50 | "minimum-stability": "stable",
51 | "config": {
52 | "optimize-autoloader": true,
53 | "allow-plugins": {
54 | "dealerdirect/phpcodesniffer-composer-installer": true
55 | }
56 | },
57 | "scripts" : {
58 | "phpcompat": [
59 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs -ps . --standard=PHPCompatibility --exclude=PHPCompatibility.Attributes.NewAttributes --ignore=*/vendor/* --extensions=php --runtime-set testVersion 7.0-"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 | ./src
15 |
16 | ./.build
17 | ./tests
18 | ./vendor
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ./tests/src/Unit
28 |
29 |
30 | ./tests/src/Functional
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/Cortex.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain;
12 |
13 | use Brain\Cortex\Group\GroupCollection;
14 | use Brain\Cortex\Group\GroupCollectionInterface;
15 | use Brain\Cortex\Route\PriorityRouteCollection;
16 | use Brain\Cortex\Route\RouteCollectionInterface;
17 | use Brain\Cortex\Router\ResultHandler;
18 | use Brain\Cortex\Router\ResultHandlerInterface;
19 | use Brain\Cortex\Router\Router;
20 | use Brain\Cortex\Router\RouterInterface;
21 | use Brain\Cortex\Uri\PsrUri;
22 | use Brain\Cortex\Uri\WordPressUri;
23 | use Brain\Cortex\Uri\UriInterface;
24 | use Psr\Http\Message\RequestInterface;
25 |
26 | /**
27 | * @author Giuseppe Mazzapica
28 | * @license http://opensource.org/licenses/MIT MIT
29 | * @package Cortex
30 | */
31 | class Cortex
32 | {
33 | /**
34 | * @var bool
35 | */
36 | private static $booted = false;
37 |
38 | /**
39 | * @var bool
40 | */
41 | private static $late = false;
42 |
43 | /**
44 | * @param \Psr\Http\Message\RequestInterface $request
45 | * @return bool
46 | * @throws \Exception
47 | */
48 | public static function boot(RequestInterface $request = null)
49 | {
50 | try {
51 | if (self::$booted) {
52 | return false;
53 | }
54 |
55 | if (did_action('parse_request')) {
56 | throw new \BadMethodCallException(
57 | sprintf('%s must be called before "do_parse_request".', __METHOD__)
58 | );
59 | }
60 |
61 | self::$booted = add_filter('do_parse_request', function ($do, \WP $wp) use ($request) {
62 | self::$late = true;
63 | try {
64 | $instance = new static();
65 | $do = $instance->doBoot($wp, $do, $request);
66 | unset($instance);
67 |
68 | if ( ! $do ) {
69 | global $wp_version;
70 |
71 | if ( $wp_version && version_compare( $wp_version, '6', '>=' ) ) {
72 | $wp->query_posts();
73 | $wp->register_globals();
74 | }
75 | }
76 |
77 | return $do;
78 | } catch (\Exception $e) {
79 | if (defined('WP_DEBUG') && WP_DEBUG) {
80 | throw $e;
81 | }
82 |
83 | do_action('cortex.fail', $e);
84 |
85 | return $do;
86 | }
87 | }, 100, 2);
88 | } catch (\Exception $e) {
89 | if (defined('WP_DEBUG') && WP_DEBUG) {
90 | throw $e;
91 | }
92 |
93 | do_action('cortex.fail', $e);
94 | }
95 |
96 | return true;
97 | }
98 |
99 | /**
100 | * @return bool
101 | */
102 | public static function late()
103 | {
104 | return self::$late;
105 | }
106 |
107 | /**
108 | * @param \WP $wp
109 | * @param bool $do
110 | * @param \Psr\Http\Message\RequestInterface|null $request
111 | * @return bool
112 | */
113 | private function doBoot(\WP $wp, $do, RequestInterface $request = null)
114 | {
115 | $uri = $this->factoryUri($request);
116 | $method = $this->getMethod($request);
117 | $routes = $this->factoryRoutes($uri, $method);
118 | $groups = $this->factoryGroups();
119 | $router = $this->factoryRouter($routes, $groups);
120 | $handler = $this->factoryHandler();
121 | add_filter('cortex.match.done', function ($result) {
122 | remove_all_filters('cortex.routes');
123 | remove_all_filters('cortex.groups');
124 |
125 | return $result;
126 | });
127 | $do = $handler->handle($router->match($uri, $method), $wp, $do);
128 | is_bool($do) or $do = true;
129 |
130 | return $do;
131 | }
132 |
133 | /**
134 | * @param string $name
135 | * @param string|null $abstract
136 | * @param callable|null $default
137 | * @return object
138 | */
139 | private function factoryByHook($name, $abstract = null, callable $default = null)
140 | {
141 | $thing = apply_filters("cortex.{$name}.instance", null);
142 | if (
143 | is_string($abstract)
144 | && (class_exists($abstract) || interface_exists($abstract))
145 | && (! is_object($thing) || ! is_subclass_of($thing, $abstract, true))
146 | ) {
147 | $thing = is_callable($default) ? $default() : null;
148 | }
149 |
150 | if (! is_object($thing)) {
151 | throw new \RuntimeException(sprintf('Impossible to factory "%s".', $name));
152 | }
153 |
154 | return $thing;
155 | }
156 |
157 | /**
158 | * @param \Psr\Http\Message\RequestInterface $request
159 | * @return \Brain\Cortex\Uri\UriInterface
160 | */
161 | private function factoryUri(RequestInterface $request = null)
162 | {
163 | $psrUri = is_null($request) ? null : $request->getUri();
164 |
165 | /** @var UriInterface $uri */
166 | $uri = $this->factoryByHook(
167 | 'uri',
168 | UriInterface::class,
169 | function () use ($psrUri) {
170 | is_null($psrUri) and $psrUri = new PsrUri();
171 |
172 | return new WordPressUri($psrUri);
173 | }
174 | );
175 |
176 | return $uri;
177 | }
178 |
179 | /**
180 | * @param \Psr\Http\Message\RequestInterface|null $request
181 | * @return string
182 | */
183 | private function getMethod(RequestInterface $request = null)
184 | {
185 | if ($request) {
186 | return $request->getMethod();
187 | }
188 |
189 | return empty($_SERVER['REQUEST_METHOD']) ? 'GET' : strtoupper($_SERVER['REQUEST_METHOD']);
190 | }
191 |
192 | /**
193 | * @return \Brain\Cortex\Group\GroupCollectionInterface
194 | */
195 | private function factoryGroups()
196 | {
197 | /** @var \Brain\Cortex\Group\GroupCollectionInterface $groups */
198 | $groups = $this->factoryByHook(
199 | 'group-collection',
200 | GroupCollectionInterface::class,
201 | function () {
202 | return new GroupCollection();
203 | }
204 | );
205 |
206 | do_action('cortex.groups', $groups);
207 |
208 | return $groups;
209 | }
210 |
211 | /**
212 | * @param \Brain\Cortex\Uri\UriInterface $uri
213 | * @param string $method
214 | * @return \Brain\Cortex\Route\RouteCollectionInterface
215 | */
216 | private function factoryRoutes(UriInterface $uri, $method)
217 | {
218 | /** @var \Brain\Cortex\Route\RouteCollectionInterface $routes */
219 | $routes = $this->factoryByHook(
220 | 'route-collection',
221 | RouteCollectionInterface::class,
222 | function () {
223 | return new PriorityRouteCollection();
224 | }
225 | );
226 |
227 | do_action('cortex.routes', $routes, $uri, $method);
228 |
229 | return $routes;
230 | }
231 |
232 | /**
233 | * @param \Brain\Cortex\Route\RouteCollectionInterface $routes
234 | * @param \Brain\Cortex\Group\GroupCollectionInterface $groups
235 | * @return \Brain\Cortex\Router\RouterInterface
236 | */
237 | private function factoryRouter(
238 | RouteCollectionInterface $routes,
239 | GroupCollectionInterface $groups
240 | ) {
241 | /** @var \Brain\Cortex\Router\RouterInterface $router */
242 | $router = $this->factoryByHook(
243 | 'router',
244 | RouterInterface::class,
245 | function () use ($routes, $groups) {
246 | return new Router($routes, $groups);
247 | }
248 | );
249 |
250 | return $router;
251 | }
252 |
253 | /**
254 | * @return \Brain\Cortex\Router\ResultHandlerInterface
255 | */
256 | private function factoryHandler()
257 | {
258 | /** @var ResultHandlerInterface $handler */
259 | $handler = $this->factoryByHook(
260 | 'result-handler',
261 | ResultHandlerInterface::class,
262 | function () {
263 | return new ResultHandler();
264 | }
265 | );
266 |
267 | return $handler;
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/Cortex/Controller/ControllerInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Controller;
12 |
13 | /**
14 | * Interface for controllers.
15 | *
16 | * Controllers runs when a route match and can be defined per route.
17 | *
18 | * @author Giuseppe Mazzapica
19 | * @license http://opensource.org/licenses/MIT MIT
20 | * @package Cortex
21 | */
22 | interface ControllerInterface
23 | {
24 | /**
25 | * @param array $vars
26 | * @param \WP $wp
27 | * @param string $template
28 | * @return bool
29 | */
30 | public function run(array $vars, \WP $wp, $template = '');
31 | }
32 |
--------------------------------------------------------------------------------
/src/Cortex/Controller/QueryVarsController.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Controller;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | final class QueryVarsController implements ControllerInterface
19 | {
20 | /**
21 | * @inheritdoc
22 | */
23 | public function run(array $vars, \WP $wp, $template = '')
24 | {
25 | $wp->query_vars = $vars;
26 |
27 | return false;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Cortex/Controller/RedirectController.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Controller;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | final class RedirectController implements ControllerInterface
19 | {
20 | /**
21 | * @codeCoverageIgnore
22 | */
23 | public static function doExit()
24 | {
25 | exit();
26 | }
27 |
28 | /**
29 | * @inheritdoc
30 | */
31 | public function run(array $vars, \WP $wp, $template = '')
32 | {
33 | $to = empty($vars['redirect_to']) ? home_url() : $vars['redirect_to'];
34 |
35 | if (filter_var($to, FILTER_VALIDATE_URL)) {
36 | $status = empty($vars['redirect_status']) ? 301 : $vars['redirect_status'];
37 | in_array((int)$status, range(300, 308), true) or $status = 301;
38 | $external = empty($vars['redirect_external']) ? false : $vars['redirect_external'];
39 | /** @var callable $cb */
40 | $cb = filter_var($external, FILTER_VALIDATE_BOOLEAN)
41 | ? 'wp_redirect'
42 | : 'wp_safe_redirect';
43 | $cb($to, $status);
44 |
45 | add_action('cortex.exit.redirect', [__CLASS__, 'doExit'], 100);
46 | do_action('cortex.exit.redirect');
47 | }
48 |
49 | return true;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Cortex/Group/Group.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Group;
12 |
13 | use Brain\Cortex\Route\Route;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | final class Group implements GroupInterface
21 | {
22 | /**
23 | * @var \Brain\Cortex\Route\Route
24 | */
25 | private $route;
26 |
27 | /**
28 | * Group constructor.
29 | *
30 | * @param array $data
31 | */
32 | public function __construct(array $data)
33 | {
34 | array_change_key_case($data, CASE_LOWER);
35 | if (isset($data['group'])) {
36 | unset($data['group']);
37 | }
38 |
39 | $data['id'] = ! empty($data['id']) && is_string($data['id'])
40 | ? $data['id']
41 | : 'group_'.spl_object_hash($this);
42 |
43 | $this->route = new Route($data);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function id()
50 | {
51 | return $this->route->id();
52 | }
53 |
54 | /**
55 | * @inheritdoc
56 | */
57 | public function toArray()
58 | {
59 | return $this->route->toArray();
60 | }
61 |
62 | /**
63 | * @inheritdoc
64 | */
65 | #[\ReturnTypeWillChange]
66 | public function offsetExists($offset)
67 | {
68 | return $this->route->offsetExists($offset);
69 | }
70 |
71 | /**
72 | * @inheritdoc
73 | */
74 | #[\ReturnTypeWillChange]
75 | public function offsetGet($offset)
76 | {
77 | return $this->route->offsetGet($offset);
78 | }
79 |
80 | /**
81 | * @inheritdoc
82 | */
83 | #[\ReturnTypeWillChange]
84 | public function offsetSet($offset, $value)
85 | {
86 | $this->route->offsetSet($offset, $value);
87 | }
88 |
89 | /**
90 | * @inheritdoc
91 | */
92 | #[\ReturnTypeWillChange]
93 | public function offsetUnset($offset)
94 | {
95 | $this->route->offsetUnset($offset);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Cortex/Group/GroupCollection.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Group;
12 |
13 | use Brain\Cortex\Route\RouteInterface;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | final class GroupCollection implements GroupCollectionInterface
21 | {
22 | /**
23 | * @var array
24 | */
25 | private $groups = [];
26 |
27 | /**
28 | * @inheritdoc
29 | */
30 | public function addGroup(GroupInterface $group)
31 | {
32 | $this->groups[$group->id()] = $group;
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function mergeGroup(RouteInterface $route)
41 | {
42 | $groups = $route->offsetGet('group');
43 | if (empty($groups)) {
44 | return $route;
45 | }
46 |
47 | $data = array_reduce((array)$groups, function (array $data, $group) {
48 | if (is_string($group) && array_key_exists($group, $this->groups)) {
49 | /** @var \Brain\Cortex\Group\GroupInterface $groupObj */
50 | $groupObj = $this->groups[$group];
51 | $data = array_merge($data, $groupObj->toArray());
52 | unset($data['id']);
53 | }
54 |
55 | return $data;
56 | }, []);
57 |
58 | $clone = clone $route;
59 | array_walk($data, function ($value, $key) use (&$clone) {
60 | $skip =
61 | in_array($key, ['id', 'group'], true)
62 | || ($clone->offsetExists($key) && ! is_null($clone->offsetGet($key)));
63 | $skip or $clone->offsetSet($key, $value);
64 | });
65 |
66 | return $clone;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Cortex/Group/GroupCollectionInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Group;
12 |
13 | use Brain\Cortex\Route\RouteInterface;
14 |
15 | /**
16 | * Interface for GroupCollection objects.
17 | * Groups objects are the way to share common settings among different routes.
18 | *
19 | * @author Giuseppe Mazzapica
20 | * @license http://opensource.org/licenses/MIT MIT
21 | * @package Cortex
22 | */
23 | interface GroupCollectionInterface
24 | {
25 | /**
26 | * @param \Brain\Cortex\Group\GroupInterface $group
27 | * @return \Brain\Cortex\Group\GroupCollectionInterface
28 | */
29 | public function addGroup(GroupInterface $group);
30 |
31 | /**
32 | * Merge group settings into a given route
33 | *
34 | * @param \Brain\Cortex\Route\RouteInterface $route
35 | * @return \Brain\Cortex\Route\RouteInterface Edited route
36 | */
37 | public function mergeGroup(RouteInterface $route);
38 | }
39 |
--------------------------------------------------------------------------------
/src/Cortex/Group/GroupInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Group;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | interface GroupInterface extends \ArrayAccess
19 | {
20 | /**
21 | * @return string
22 | */
23 | public function id();
24 |
25 | /**
26 | * @return array
27 | */
28 | public function toArray();
29 | }
30 |
--------------------------------------------------------------------------------
/src/Cortex/Route/ActionRoute.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | final class ActionRoute implements RouteInterface
19 | {
20 | use DerivativeRouteTrait;
21 |
22 | /**
23 | * @var array
24 | */
25 | private $route;
26 |
27 | /**
28 | * QueryRoute constructor.
29 | *
30 | * @param string $path
31 | * @param callable $action
32 | * @param array $options
33 | */
34 | public function __construct($path, callable $action, array $options = [])
35 | {
36 | $options['path'] = $path;
37 | $options['handler'] = $action;
38 |
39 | $this->route = new Route($options);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Cortex/Route/DerivativeRouteTrait.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | *
18 | * @property RouteInterface $route
19 | */
20 | trait DerivativeRouteTrait
21 | {
22 | /**
23 | * @see RouteInterface::id()
24 | */
25 | public function id()
26 | {
27 | return $this->route->id();
28 | }
29 |
30 | /**
31 | * @see RouteInterface::toArray()
32 | */
33 | public function toArray()
34 | {
35 | return $this->route->toArray();
36 | }
37 |
38 | /**
39 | * @see RouteInterface::offsetExists()
40 | * @param string $offset
41 | * @return bool
42 | */
43 | #[\ReturnTypeWillChange]
44 | public function offsetExists($offset)
45 | {
46 | return $this->route->offsetExists($offset);
47 | }
48 |
49 | /**
50 | * @see RouteInterface::offsetGet()
51 | * @param string $offset
52 | * @return mixed
53 | */
54 | #[\ReturnTypeWillChange]
55 | public function offsetGet($offset)
56 | {
57 | return $this->route->offsetGet($offset);
58 | }
59 |
60 | /**
61 | * @see RouteInterface::offsetSet()
62 | * @param string $offset
63 | * @param mixed $value
64 | */
65 | #[\ReturnTypeWillChange]
66 | public function offsetSet($offset, $value)
67 | {
68 | $this->route->offsetSet($offset, $value);
69 | }
70 |
71 | /**
72 | * @see RouteInterface::offsetUnset()
73 | * @param string $offset
74 | */
75 | #[\ReturnTypeWillChange]
76 | public function offsetUnset($offset)
77 | {
78 | $this->route->offsetUnset($offset);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Cortex/Route/PriorityRouteCollection.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | final class PriorityRouteCollection implements RouteCollectionInterface
19 | {
20 | private static $pagedFlags = [RouteInterface::PAGED_ARCHIVE, RouteInterface::PAGED_SINGLE];
21 |
22 | /**
23 | * @var \SplPriorityQueue
24 | */
25 | private $queue;
26 |
27 | /**
28 | * @var array
29 | */
30 | private $priorities = [];
31 |
32 | /**
33 | * PriorityRouteCollection constructor.
34 | */
35 | public function __construct()
36 | {
37 | $queue = new \SplPriorityQueue();
38 | $queue->setExtractFlags(\SplPriorityQueue::EXTR_DATA);
39 | $this->queue = $queue;
40 | }
41 |
42 | /**
43 | * @param \Brain\Cortex\Route\RouteInterface $route
44 | * @return \Brain\Cortex\Route\RouteCollectionInterface
45 | */
46 | public function addRoute(RouteInterface $route)
47 | {
48 | if (! $route->offsetExists('priority') || ! is_numeric($route->offsetGet('priority'))) {
49 | $next = $this->priorities ? max($this->priorities) + 1 : 10;
50 | $route->offsetSet('priority', $next);
51 | }
52 |
53 | $priority = $route->offsetGet('priority');
54 |
55 | $paged = $this->maybeBuildPaged($route);
56 | if (
57 | $paged instanceof RouteInterface
58 | && ! in_array($paged['paged'], self::$pagedFlags, true) // ensure avoid infinite loops
59 | ) {
60 | $paged->offsetSet('priority', $priority);
61 | $priority++;
62 | $this->addRoute($paged);
63 | }
64 |
65 | in_array($priority, $this->priorities, true) or $this->priorities[] = $priority;
66 | $this->queue->insert($route, ((-1) * $priority));
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * @inheritdoc
73 | */
74 | #[\ReturnTypeWillChange]
75 | public function current()
76 | {
77 | return $this->queue->current();
78 | }
79 |
80 | /**
81 | * @inheritdoc
82 | */
83 | #[\ReturnTypeWillChange]
84 | public function next()
85 | {
86 | $this->queue->next();
87 | }
88 |
89 | /**
90 | * @inheritdoc
91 | */
92 | #[\ReturnTypeWillChange]
93 | public function key()
94 | {
95 | return $this->queue->key();
96 | }
97 |
98 | /**
99 | * @inheritdoc
100 | */
101 | #[\ReturnTypeWillChange]
102 | public function valid()
103 | {
104 | return $this->queue->valid();
105 | }
106 |
107 | /**
108 | * @inheritdoc
109 | */
110 | #[\ReturnTypeWillChange]
111 | public function rewind()
112 | {
113 | $this->queue->rewind();
114 | }
115 |
116 | /**
117 | * @inheritdoc
118 | */
119 | #[\ReturnTypeWillChange]
120 | public function count()
121 | {
122 | return $this->queue->count();
123 | }
124 |
125 | /**
126 | * @param \Brain\Cortex\Route\RouteInterface $route
127 | * @return \Brain\Cortex\Route\RouteInterface|null
128 | */
129 | private function maybeBuildPaged(RouteInterface $route)
130 | {
131 | $built = null;
132 | $pagedArg = $route->offsetExists('paged') ? $route->offsetGet('paged') : '';
133 | $path = $route->offsetExists('path') ? $route->offsetGet('path') : '';
134 | if (in_array($pagedArg, self::$pagedFlags, true) && $path && is_string($path)) {
135 | $base = 'page';
136 | /** @var \WP_Rewrite $wp_rewrite */
137 | global $wp_rewrite;
138 | $wp_rewrite instanceof \WP_Rewrite and $base = $wp_rewrite->pagination_base;
139 | $array = $route->toArray();
140 | $array['id'] = $route->id().'_paged';
141 | $array['paged'] = RouteInterface::PAGED_UNPAGED;
142 | $array['path'] = $pagedArg === RouteInterface::PAGED_ARCHIVE
143 | ? $path.'/'.$base.'/{paged:\d+}'
144 | : $path.'/{page:\d+}';
145 |
146 | $routeVars = $route->offsetExists('vars') ? $route->offsetGet('vars') : [];
147 | if (is_callable($routeVars)) {
148 | $array['vars'] = $this->buildPagedVars($routeVars, $pagedArg);
149 | }
150 |
151 | $built = apply_filters('cortex.paged-route', new Route($array), $route);
152 | }
153 |
154 | return $built;
155 | }
156 |
157 | /**
158 | * When route `vars` property is a callback and the route is paged, we ensure that the `vars`
159 | * callable receives the proper `paged` (or `page`) query argument and also that it returned
160 | * array includes `paged` (or `page`) query argument.
161 | *
162 | * @param callable $routeVars
163 | * @param string $pagedArg
164 | * @return \Closure
165 | */
166 | private function buildPagedVars(callable $routeVars, $pagedArg)
167 | {
168 | $key = $pagedArg === RouteInterface::PAGED_SINGLE ? 'page' : 'paged';
169 |
170 | return function (array $vars) use ($routeVars, $key) {
171 | (isset($vars[$key]) && is_numeric($vars[$key])) or $vars[$key] = 1;
172 | $vars[$key] = (int)$vars[$key];
173 | $result = $routeVars($vars);
174 | if (is_array($result) && ! (isset($result[$key]) && is_numeric($result[$key]))) {
175 | $result[$key] = $vars[$key];
176 | }
177 | $result[$key] = (int)$result[$key];
178 |
179 | return $result;
180 | };
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/Cortex/Route/QueryRoute.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | use Brain\Cortex\Controller\ControllerInterface;
14 | use Brain\Cortex\Controller\QueryVarsController;
15 |
16 | /**
17 | * @author Giuseppe Mazzapica
18 | * @license http://opensource.org/licenses/MIT MIT
19 | * @package Cortex
20 | */
21 | final class QueryRoute implements RouteInterface
22 | {
23 | use DerivativeRouteTrait;
24 |
25 | /**
26 | * @var array
27 | */
28 | private $route;
29 |
30 | /**
31 | * QueryRoute constructor.
32 | *
33 | * @param string $path
34 | * @param callable|array $queryBuilder
35 | * @param array $options
36 | */
37 | public function __construct($path, $queryBuilder, array $options = [])
38 | {
39 | $options['path'] = $path;
40 | $options['vars'] = $queryBuilder;
41 | $handler = isset($options['handler']) && $options['handler'] instanceof ControllerInterface
42 | ? $options['handler']
43 | : new QueryVarsController();
44 | $options['handler'] = $handler;
45 | $default = isset($options['default_vars']) && is_array($options['default_vars'])
46 | ? $options['default_vars']
47 | : [];
48 | if (isset($options['default']) && is_array($options['default'])) {
49 | $options['default_vars'] = array_merge($options['default'], $default);
50 | }
51 |
52 | $this->route = new Route($options);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Cortex/Route/RedirectRoute.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | use Brain\Cortex\Controller\ControllerInterface;
14 | use Brain\Cortex\Controller\RedirectController;
15 |
16 | /**
17 | * @author Giuseppe Mazzapica
18 | * @license http://opensource.org/licenses/MIT MIT
19 | * @package Cortex
20 | */
21 | final class RedirectRoute implements RouteInterface
22 | {
23 | use DerivativeRouteTrait;
24 |
25 | /**
26 | * @var \Brain\Cortex\Route\Route
27 | */
28 | private $route;
29 |
30 | /**
31 | * RedirectRoute constructor.
32 | *
33 | * @param string $from
34 | * @param string $to
35 | * @param array $options
36 | */
37 | public function __construct($from, $to, array $options = [])
38 | {
39 | list($vars, $options) = $this->parseOptions($options);
40 |
41 | $options['vars'] = is_callable($to)
42 | ? $this->redirectToFromCallback($to, $vars)
43 | : array_merge($vars, ['redirect_to' => $this->redirectToFromString($to, $vars)]);
44 |
45 | $options['path'] = $from;
46 | $handler = isset($options['handler']) && $options['handler'] instanceof ControllerInterface
47 | ? $options['handler']
48 | : new RedirectController();
49 | $options['handler'] = $handler;
50 |
51 | $this->route = new Route($options);
52 | }
53 |
54 | /**
55 | * @param array $options
56 | * @return array
57 | */
58 | private function parseOptions(array $options)
59 | {
60 | $status = empty($options['redirect_status']) ? 301 : $options['redirect_status'];
61 | in_array((int)$status, range(300, 308), true) or $status = 301;
62 |
63 | $external = empty($options['redirect_external']) ? false : $options['redirect_external'];
64 |
65 | $vars = [
66 | 'redirect_status' => $status,
67 | 'redirect_external' => filter_var($external, FILTER_VALIDATE_BOOLEAN),
68 | 'redirect_to' => null,
69 | ];
70 |
71 | return [$vars, array_diff_key($options, $vars)];
72 | }
73 |
74 | /**
75 | * @param callable $to
76 | * @param array $vars
77 | * @return \Closure
78 | */
79 | private function redirectToFromCallback(callable $to, array $vars)
80 | {
81 | return function (array $args) use ($to, $vars) {
82 | $vars['redirect_to'] = $this->redirectToFromString($to($args), $vars);
83 |
84 | return $vars;
85 | };
86 | }
87 |
88 | /**
89 | * @param string $url
90 | * @param array $vars
91 | * @return string
92 | */
93 | private function redirectToFromString($url, array $vars)
94 | {
95 | if (! is_string($url)) {
96 | return '';
97 | }
98 |
99 | $url = filter_var($url, FILTER_SANITIZE_URL);
100 | if (empty($url)) {
101 | return '';
102 | }
103 |
104 | $valid = filter_var($url, FILTER_VALIDATE_URL);
105 |
106 | if ($vars['redirect_external']) {
107 | return $valid ? $url : '';
108 | }
109 |
110 | return $valid ? $url : home_url($url);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Cortex/Route/Route.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | final class Route implements RouteInterface
19 | {
20 | /**
21 | * @var array
22 | */
23 | private static $defaults = [
24 | 'vars' => null,
25 | 'default_vars' => null,
26 | 'host' => null,
27 | 'priority' => null,
28 | 'group' => null,
29 | 'merge_query_string' => null,
30 | 'paged' => null,
31 | 'method' => null,
32 | 'scheme' => null,
33 | 'before' => null,
34 | 'after' => null,
35 | 'template' => null,
36 | 'no_template' => null,
37 | 'path' => null,
38 | 'handler' => null,
39 | ];
40 |
41 | /**
42 | * @var array
43 | */
44 | private $storage = [];
45 |
46 | /**
47 | * Route constructor.
48 | *
49 | * @param array $data
50 | */
51 | public function __construct(array $data)
52 | {
53 | array_change_key_case($data, CASE_LOWER);
54 | $id = ! empty($data['id']) && is_string($data['id'])
55 | ? $data['id']
56 | : 'route_'.spl_object_hash($this);
57 | $storage = array_merge(self::$defaults, $data);
58 | $storage['id'] = $id;
59 | $this->storage = $storage;
60 | }
61 |
62 | /**
63 | * @inheritdoc
64 | */
65 | public function id()
66 | {
67 | return $this->storage['id'];
68 | }
69 |
70 | /**
71 | * @inheritdoc
72 | */
73 | public function toArray()
74 | {
75 | $result = [];
76 | foreach ($this->storage as $key => $value) {
77 | (is_string($key) && $key && ! is_null($value)) and $result[$key] = $value;
78 | }
79 |
80 | return $result;
81 | }
82 |
83 | /**
84 | * @inheritdoc
85 | */
86 | #[\ReturnTypeWillChange]
87 | public function offsetExists($offset)
88 | {
89 | return array_key_exists($offset, $this->storage);
90 | }
91 |
92 | /**
93 | * @inheritdoc
94 | */
95 | #[\ReturnTypeWillChange]
96 | public function offsetGet($offset)
97 | {
98 | return $this->offsetExists($offset) ? $this->storage[$offset] : null;
99 | }
100 |
101 | /**
102 | * @inheritdoc
103 | */
104 | #[\ReturnTypeWillChange]
105 | public function offsetSet($offset, $value)
106 | {
107 | $this->storage[$offset] = $value;
108 | }
109 |
110 | /**
111 | * @inheritdoc
112 | */
113 | #[\ReturnTypeWillChange]
114 | public function offsetUnset($offset)
115 | {
116 | if ($this->offsetExists($offset) && $offset !== 'id') {
117 | unset($this->storage[$offset]);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Cortex/Route/RouteCollectionInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | interface RouteCollectionInterface extends \Iterator, \Countable
19 | {
20 | /**
21 | * @param \Brain\Cortex\Route\RouteInterface $route
22 | * @return \Brain\Cortex\Route\RouteCollectionInterface
23 | */
24 | public function addRoute(RouteInterface $route);
25 | }
26 |
--------------------------------------------------------------------------------
/src/Cortex/Route/RouteInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Route;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | interface RouteInterface extends \ArrayAccess
19 | {
20 | const PAGED_SINGLE = 'paged_single';
21 | const PAGED_ARCHIVE = 'paged_archive';
22 | const PAGED_UNPAGED = '';
23 | const PAGED_SEARCH = 'paged_archive';
24 | const PAGED_FRONT_PAGE = 'paged_archive';
25 | const NOT_PAGED = 'not_paged';
26 |
27 | /**
28 | * @return string
29 | */
30 | public function id();
31 |
32 | /**
33 | * @return array
34 | */
35 | public function toArray();
36 | }
37 |
--------------------------------------------------------------------------------
/src/Cortex/Router/MatchingResult.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | use Brain\Cortex\Controller\ControllerInterface as Controller;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | class MatchingResult
21 | {
22 | /**
23 | * @var array
24 | */
25 | private $data;
26 |
27 | /**
28 | * MatchingResult constructor.
29 | *
30 | * @param array $data
31 | */
32 | public function __construct(array $data)
33 | {
34 | $defaults = [
35 | 'route' => null,
36 | 'path' => null,
37 | 'vars' => null,
38 | 'matches' => null,
39 | 'handler' => null,
40 | 'before' => null,
41 | 'after' => null,
42 | 'template' => null,
43 | ];
44 |
45 | $this->data = array_merge($defaults, array_change_key_case($data, CASE_LOWER));
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function route()
52 | {
53 | return is_string($this->data['route']) ? $this->data['route'] : '';
54 | }
55 |
56 | /**
57 | * @return string
58 | */
59 | public function matchedPath()
60 | {
61 | return is_string($this->data['path']) ? $this->data['path'] : '';
62 | }
63 |
64 | /**
65 | * @return array
66 | */
67 | public function vars()
68 | {
69 | return is_array($this->data['vars']) ? $this->data['vars'] : [];
70 | }
71 |
72 | /**
73 | * @return array
74 | */
75 | public function matches()
76 | {
77 | return is_array($this->data['matches']) ? $this->data['matches'] : [];
78 | }
79 |
80 | /**
81 | * @return bool
82 | */
83 | public function matched()
84 | {
85 | return $this->route() && $this->matchedPath();
86 | }
87 |
88 | /**
89 | * @return string|bool|callable
90 | */
91 | public function template()
92 | {
93 | $template = $this->data['template'];
94 |
95 | return (is_string($template) || $template === false || is_callable($template))
96 | ? $template
97 | : '';
98 | }
99 |
100 | /**
101 | * @return callable|\Brain\Cortex\Controller\ControllerInterface|null
102 | */
103 | public function handler()
104 | {
105 | return is_callable($this->data['handler']) || $this->data['handler'] instanceof Controller
106 | ? $this->data['handler']
107 | : null;
108 | }
109 |
110 | /**
111 | * @return callable|\Brain\Cortex\Controller\ControllerInterface|null
112 | */
113 | public function beforeHandler()
114 | {
115 | return is_callable($this->data['before']) || $this->data['before'] instanceof Controller
116 | ? $this->data['before']
117 | : null;
118 | }
119 |
120 | /**
121 | * @return callable|\Brain\Cortex\Controller\ControllerInterface|null
122 | */
123 | public function afterHandler()
124 | {
125 | return is_callable($this->data['after']) || $this->data['after'] instanceof Controller
126 | ? $this->data['after']
127 | : null;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Cortex/Router/ResultHandler.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | use Brain\Cortex\Controller\ControllerInterface;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | final class ResultHandler implements ResultHandlerInterface
21 | {
22 | /**
23 | * @inheritdoc
24 | */
25 | public function handle(MatchingResult $result, \WP $wp, $doParseRequest)
26 | {
27 | /** @var \Brain\Cortex\Router\MatchingResult $result */
28 | $result = apply_filters('cortex.match.done', $result, $wp, $doParseRequest);
29 | $handlerResult = $doParseRequest;
30 |
31 | if (!$result instanceof MatchingResult) {
32 | return $result;
33 | }
34 |
35 | /** @var \Brain\Cortex\Router\MatchingResult $result */
36 | if ($result->matched()) {
37 | $doParseRequest = false;
38 | $origHandler = $result->handler();
39 | $handler = $this->buildCallback($origHandler);
40 | $before = $this->buildCallback($result->beforeHandler());
41 | $after = $this->buildCallback($result->afterHandler());
42 | $template = $result->template();
43 | $vars = $result->vars();
44 | $matches = $result->matches();
45 |
46 | if (is_callable($template)) {
47 | $template = $template($vars, $wp, $matches);
48 | }
49 |
50 | (is_string($template) || $template === 'false') or $template = '';
51 |
52 | do_action('cortex.matched', $result, $wp);
53 |
54 | is_callable($before) and $before($vars, $wp, $template, $matches);
55 | is_callable($handler) and $handlerResult = $handler($vars, $wp, $template, $matches);
56 | is_callable($after) and $after($vars, $wp, $template, $matches);
57 | $this->setTemplate($template);
58 |
59 | do_action('cortex.matched-after', $result, $wp, $handlerResult);
60 |
61 | is_bool($handlerResult) and $doParseRequest = $handlerResult;
62 | $doParseRequest = apply_filters('cortex.do-parse-request', $doParseRequest);
63 |
64 | if (!$doParseRequest) {
65 | remove_filter('template_redirect', 'redirect_canonical');
66 |
67 | return false;
68 | }
69 | }
70 |
71 | do_action('cortex.result.done', $result, $wp, $handlerResult);
72 |
73 | return $doParseRequest;
74 | }
75 |
76 | /**
77 | * @param mixed $handler
78 | * @return callable|null
79 | */
80 | private function buildCallback($handler)
81 | {
82 | $built = null;
83 | if (is_callable($handler)) {
84 | $built = $handler;
85 | }
86 |
87 | if (!$built && $handler instanceof ControllerInterface) {
88 | $built = function (array $vars, \WP $wp, $template) use ($handler) {
89 | return $handler->run($vars, $wp, $template);
90 | };
91 | }
92 |
93 | return $built;
94 | }
95 |
96 | /**
97 | * @param $template
98 | */
99 | private function setTemplate($template)
100 | {
101 | if (is_string($template) && $template) {
102 | $ext = apply_filters('cortex.default-template-extension', 'php');
103 | pathinfo($template, PATHINFO_EXTENSION) or $template .= '.' . ltrim($ext, '.');
104 | $template = is_file($template) ? $template : locate_template([$template], false);
105 | $template or $template = '';
106 | }
107 |
108 | if ($template === '' || !(is_string($template) || $template === false)) {
109 | return;
110 | }
111 |
112 | $types = [
113 | '404',
114 | 'search',
115 | 'front_page',
116 | 'home',
117 | 'archive',
118 | 'taxonomy',
119 | 'attachment',
120 | 'single',
121 | 'page',
122 | 'singular',
123 | 'category',
124 | 'tag',
125 | 'author',
126 | 'date',
127 | 'paged',
128 | 'index',
129 | ];
130 |
131 | $returnTemplate = function () use ($template) {
132 | current_filter() === 'template_include' and remove_all_filters('template_include');
133 |
134 | return $template;
135 | };
136 |
137 | /*
138 | * If template is `false`, we return `true` on `"{$type}_template"` to speed up
139 | * `template-loader.php`, in fact, if template is `false` we are going to return `false` at
140 | * 'template_include' anyway, so no need to waste time looping all template types, even
141 | * because every type check for file(s) in filesystem, so it's a slow operation.
142 | */
143 | $template_setter = $template !== false ? $returnTemplate : '__return_true';
144 |
145 | array_walk($types, function ($type) use ($template_setter) {
146 | add_filter("{$type}_template", $template_setter);
147 | });
148 |
149 | add_filter('template_include', $returnTemplate, -1);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Cortex/Router/ResultHandlerInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | interface ResultHandlerInterface
19 | {
20 | /**
21 | * @param \Brain\Cortex\Router\MatchingResult $result
22 | * @param \WP $wp
23 | * @param bool $doParseRequest
24 | * @return bool
25 | */
26 | public function handle(MatchingResult $result, \WP $wp, $doParseRequest);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Cortex/Router/RouteFilterIterator.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | use Brain\Cortex\Route\RouteCollectionInterface;
14 | use Brain\Cortex\Route\RouteInterface;
15 | use Brain\Cortex\Uri\UriInterface;
16 |
17 | /**
18 | * @author Giuseppe Mazzapica
19 | * @license http://opensource.org/licenses/MIT MIT
20 | * @package Cortex
21 | */
22 | final class RouteFilterIterator extends \FilterIterator
23 | {
24 | /**
25 | * @var \Brain\Cortex\Uri\WordPressUri
26 | */
27 | private $uri;
28 |
29 | /**
30 | * @param string $routeHost
31 | * @param string $serverHost
32 | * @return bool
33 | */
34 | private static function checkHost($routeHost, $serverHost)
35 | {
36 | if (strpos($routeHost, '*') === false) {
37 | return $routeHost === $serverHost;
38 | }
39 |
40 | if (strpos($routeHost, '*.') === 0) {
41 | return fnmatch($routeHost, $serverHost) || fnmatch(substr($routeHost, 2), $serverHost);
42 | }
43 |
44 | return fnmatch($routeHost, $serverHost);
45 | }
46 |
47 | /**
48 | * RouteFilterIterator constructor.
49 | *
50 | * @param \Brain\Cortex\Route\RouteCollectionInterface $routes
51 | * @param \Brain\Cortex\Uri\UriInterface $uri
52 | */
53 | public function __construct(RouteCollectionInterface $routes, UriInterface $uri)
54 | {
55 | parent::__construct($routes);
56 | $this->uri = $uri;
57 | }
58 |
59 | /**
60 | * @inheritdoc
61 | */
62 | #[\ReturnTypeWillChange]
63 | public function accept()
64 | {
65 | /** @var RouteInterface $route */
66 | $route = $this->getInnerIterator()->current();
67 | if (! $route instanceof RouteInterface) {
68 | return false;
69 | }
70 |
71 | $scheme = strtolower((string)$route['scheme']);
72 | if (! empty($scheme) && $scheme !== $this->uri->scheme()) {
73 | return false;
74 | }
75 |
76 | $host = filter_var(strtolower((string)$route['host']), FILTER_SANITIZE_URL);
77 | if (! empty($host) && ! self::checkHost($route['host'], $this->uri->host())) {
78 | return false;
79 | }
80 |
81 | return true;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Cortex/Router/Router.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | use Brain\Cortex\Controller\ControllerInterface;
14 | use Brain\Cortex\Group\GroupCollectionInterface;
15 | use Brain\Cortex\Route\RouteCollectionInterface;
16 | use Brain\Cortex\Route\RouteInterface;
17 | use Brain\Cortex\Uri\UriInterface;
18 | use FastRoute\DataGenerator\GroupCountBased as DefDataGenerator;
19 | use FastRoute\Dispatcher;
20 | use FastRoute\Dispatcher\GroupCountBased as DefDispatcher;
21 | use FastRoute\RouteCollector;
22 | use FastRoute\RouteParser\Std;
23 |
24 | /**
25 | * @author Giuseppe Mazzapica
26 | * @license http://opensource.org/licenses/MIT MIT
27 | * @package Cortex
28 | */
29 | final class Router implements RouterInterface
30 | {
31 | /**
32 | * @var \Brain\Cortex\Group\GroupCollectionInterface
33 | */
34 | private $groups;
35 |
36 | /**
37 | * @var \Brain\Cortex\Route\RouteCollectionInterface
38 | */
39 | private $routes;
40 |
41 | /**
42 | * @var \FastRoute\RouteCollector
43 | */
44 | private $collector;
45 |
46 | /**
47 | * @var callable
48 | */
49 | private $dispatcherFactory;
50 |
51 | /*
52 | * @var array
53 | */
54 | private $parsedRoutes = [];
55 |
56 | /**
57 | * @var null
58 | */
59 | private $results = null;
60 |
61 | /**
62 | * Router constructor.
63 | *
64 | * @param \Brain\Cortex\Route\RouteCollectionInterface $routes
65 | * @param \Brain\Cortex\Group\GroupCollectionInterface $groups
66 | * @param \FastRoute\RouteCollector $collector
67 | * @param callable $dispatcherFactory
68 | */
69 | public function __construct(
70 | RouteCollectionInterface $routes,
71 | GroupCollectionInterface $groups,
72 | RouteCollector $collector = null,
73 | callable $dispatcherFactory = null
74 | ) {
75 | $this->groups = $groups;
76 | $this->routes = $routes;
77 | $this->collector = $collector ? : new RouteCollector(new Std(), new DefDataGenerator());
78 | $this->dispatcherFactory = $dispatcherFactory;
79 | }
80 |
81 | /**
82 | * @inheritdoc
83 | */
84 | public function match(UriInterface $uri, $httpMethod)
85 | {
86 | if ($this->results instanceof MatchingResult) {
87 | return $this->results;
88 | }
89 |
90 | if (! count($this->routes) || ! $this->parseRoutes($uri, $httpMethod)) {
91 | $this->results = new MatchingResult(['route' => null]);
92 |
93 | return $this->results;
94 | }
95 |
96 | // in case of exact match, no need to go further
97 | if ($this->results instanceof MatchingResult) {
98 | return $this->results;
99 | }
100 |
101 | $dispatcher = $this->buildDispatcher($this->collector->getData());
102 | unset($this->collector);
103 |
104 | $uriPath = '/'.trim($uri->path(), '/');
105 | $routeInfo = $dispatcher->dispatch($httpMethod, $uriPath ? : '/');
106 | if ($routeInfo[0] === Dispatcher::FOUND) {
107 | $route = $this->parsedRoutes[$routeInfo[1]];
108 | $vars = $routeInfo[2];
109 |
110 | $this->results = $this->finalizeRoute($route, $vars, $uri);
111 | }
112 |
113 | $this->results or $this->results = new MatchingResult(['route' => null]);
114 |
115 | unset($this->parsedRoutes);
116 |
117 | return $this->results;
118 | }
119 |
120 | /**
121 | * @param \Brain\Cortex\Uri\UriInterface $uri
122 | * @param string $httpMethod
123 | * @return int
124 | */
125 | private function parseRoutes(UriInterface $uri, $httpMethod)
126 | {
127 | $iterator = new RouteFilterIterator($this->routes, $uri);
128 | $parsed = 0;
129 | /** @var \Brain\Cortex\Route\RouteInterface $route */
130 | foreach ($iterator as $route) {
131 | $route = $this->sanitizeRouteMethod($this->groups->mergeGroup($route), $httpMethod);
132 | if (! $this->validateRoute($route, $httpMethod)) {
133 | continue;
134 | }
135 |
136 | $parsed++;
137 | $id = $route->id();
138 | $this->parsedRoutes[$id] = $route;
139 | $path = '/'.trim((string)$route['path'], '/');
140 | // exact match
141 | if ($path === '/'.trim($uri->path(), '/')) {
142 | $this->results = $this->finalizeRoute($route, [], $uri);
143 | unset($this->parsedRoutes, $this->collector);
144 | break;
145 | }
146 |
147 | $this->collector->addRoute(strtoupper($route['method']), $path, $id);
148 | }
149 |
150 | unset($this->routes, $this->groups);
151 |
152 | return $parsed;
153 | }
154 |
155 | /**
156 | * @param \Brain\Cortex\Route\RouteInterface $route
157 | * @param string $httpMethod
158 | * @return \Brain\Cortex\Route\RouteInterface
159 | */
160 | private function sanitizeRouteMethod(RouteInterface $route, $httpMethod)
161 | {
162 | if (empty($route['method']) || ! (is_string($route['method']) || is_array($route['method']))) {
163 | $route['method'] = $httpMethod;
164 | }
165 |
166 | if (is_array($route['method'])) {
167 | $route['method'] = array_map('strtoupper', array_filter($route['method'], 'is_string'));
168 |
169 | return $route;
170 | }
171 |
172 | (strtolower($route['method']) === 'any') and $route['method'] = $httpMethod;
173 |
174 | $route['method'] = strtoupper($route['method']);
175 |
176 | return $route;
177 | }
178 |
179 | /**
180 | * @param \Brain\Cortex\Route\RouteInterface $route
181 | * @param $httpMethod
182 | * @return bool
183 | */
184 | private function validateRoute(RouteInterface $route, $httpMethod)
185 | {
186 | $id = $route->id();
187 | $path = trim((string)$route['path'], '/');
188 | $handler = $route['handler'];
189 |
190 | return
191 | is_string($id)
192 | && $id
193 | && filter_var($path, FILTER_SANITIZE_URL) === $path
194 | && in_array($httpMethod, (array) $route['method'], true)
195 | && (is_callable($handler) || $handler instanceof ControllerInterface);
196 | }
197 |
198 | /**
199 | * @param array $data
200 | * @return \FastRoute\Dispatcher
201 | */
202 | private function buildDispatcher(array $data)
203 | {
204 | $dispatcher = null;
205 | if (is_callable($this->dispatcherFactory)) {
206 | $factory = $this->dispatcherFactory;
207 | $dispatcher = $factory($data);
208 | }
209 |
210 | $dispatcher instanceof Dispatcher or $dispatcher = new DefDispatcher($data);
211 |
212 | return $dispatcher;
213 | }
214 |
215 | /**
216 | * @param \Brain\Cortex\Route\RouteInterface $route
217 | * @param array $vars
218 | * @param \Brain\Cortex\Uri\UriInterface $uri
219 | * @return \Brain\Cortex\Router\MatchingResult
220 | */
221 | private function finalizeRoute(RouteInterface $route, array $vars, UriInterface $uri)
222 | {
223 | is_null($route['merge_query_string']) and $route['merge_query_string'] = true;
224 | $merge = filter_var($route['merge_query_string'], FILTER_VALIDATE_BOOLEAN);
225 | $uriVars = $uri->vars();
226 | $merge and $vars = array_merge($vars, $uriVars);
227 | // vars is going to be modified if route vars is a callback, lets save this as a backup
228 | $varsOriginal = $vars;
229 | $result = null;
230 | switch (true) {
231 | case (is_callable($route['vars'])) :
232 | /** @var callable $cb */
233 | $cb = $route['vars'];
234 | $routeVars = $cb($vars, $uri);
235 | is_array($routeVars) and $vars = $routeVars;
236 | $routeVars instanceof MatchingResult and $result = $routeVars;
237 | break;
238 | case (is_array($route['vars'])) :
239 | $vars = array_merge($route['vars'], $vars);
240 | break;
241 | case ($route['vars'] instanceof MatchingResult) :
242 | $result = $route['vars'];
243 | break;
244 | }
245 |
246 | if ($result instanceof MatchingResult) {
247 | return $result;
248 | }
249 |
250 | if (! empty($route['default_vars']) && is_array($route['default_vars'])) {
251 | $vars = array_merge($route['default_vars'], $vars);
252 | }
253 |
254 | $vars = $this->ensurePreviewVars($vars, $uriVars);
255 | $vars = apply_filters('cortex.matched-vars', $vars, $route, $uri);
256 | $noTemplate = filter_var($route['no_template'], FILTER_VALIDATE_BOOLEAN);
257 |
258 | return new MatchingResult([
259 | 'vars' => (array)$vars,
260 | 'matches' => (array)$varsOriginal,
261 | 'route' => $route->id(),
262 | 'path' => $route['path'],
263 | 'handler' => $route['handler'],
264 | 'before' => $route['before'],
265 | 'after' => $route['after'],
266 | 'template' => $noTemplate ? false : $route['template'],
267 | ]);
268 | }
269 |
270 | /**
271 | * To ensure preview works, we need to merge preview-related query string
272 | * to query arguments.
273 | *
274 | * @param array $vars
275 | * @param array $uriVars
276 | * @return array
277 | */
278 | private function ensurePreviewVars(array $vars, array $uriVars)
279 | {
280 | if (! is_user_logged_in()) {
281 | return $vars;
282 | }
283 |
284 | foreach (['preview', 'preview_id', 'preview_nonce'] as $var) {
285 | if (! isset($vars[$var]) && isset($uriVars[$var])) {
286 | $vars[$var] = $uriVars[$var];
287 | }
288 | }
289 |
290 | return $vars;
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/src/Cortex/Router/RouterInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Router;
12 |
13 | use Brain\Cortex\Uri\UriInterface;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | interface RouterInterface
21 | {
22 | /**
23 | * @param \Brain\Cortex\Uri\UriInterface $uri
24 | * @param string $httpMethod
25 | * @return \Brain\Cortex\Router\MatchingResult
26 | */
27 | public function match(UriInterface $uri, $httpMethod);
28 | }
29 |
--------------------------------------------------------------------------------
/src/Cortex/Uri/PsrUri.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Uri;
12 |
13 | use Psr\Http\Message\UriInterface as PsrUriInterface;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | final class PsrUri implements PsrUriInterface
21 | {
22 | /**
23 | * @var array
24 | */
25 | private $storage = [
26 | 'scheme' => 'http',
27 | 'host' => '',
28 | 'path' => '/',
29 | 'query_string' => '',
30 | ];
31 |
32 | /**
33 | * @var array
34 | */
35 | private $server;
36 |
37 | /**
38 | * @var bool
39 | */
40 | private $parsed = false;
41 |
42 | /**
43 | * WordPressUri constructor.
44 | *
45 | * @param array $server
46 | */
47 | public function __construct(array $server = [])
48 | {
49 | $this->server = $server ? : $_SERVER;
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function getScheme()
56 | {
57 | $this->parsed or $this->marshallFromServer();
58 |
59 | return $this->storage['scheme'];
60 | }
61 |
62 | /**
63 | * @inheritdoc
64 | */
65 | public function getHost()
66 | {
67 | $this->parsed or $this->marshallFromServer();
68 |
69 | return $this->storage['host'];
70 | }
71 |
72 | /**
73 | * Not really implemented, because Cortex does not need it.
74 | *
75 | * @inheritdoc
76 | * @codeCoverageIgnore
77 | */
78 | public function getPort()
79 | {
80 | return;
81 | }
82 |
83 | /**
84 | * @inheritdoc
85 | */
86 | public function getPath()
87 | {
88 | $this->parsed or $this->marshallFromServer();
89 |
90 | return $this->storage['path'];
91 | }
92 |
93 | /**
94 | * @inheritdoc
95 | */
96 | public function getQuery()
97 | {
98 | $this->parsed or $this->marshallFromServer();
99 |
100 | return $this->storage['query_string'];
101 | }
102 |
103 | /**
104 | * Not really implemented, because Cortex does not need it.
105 | *
106 | * @inheritdoc
107 | * @codeCoverageIgnore
108 | */
109 | public function getAuthority()
110 | {
111 | return '';
112 | }
113 |
114 | /**
115 | * Not really implemented, because Cortex does not need it.
116 | *
117 | * @inheritdoc
118 | * @codeCoverageIgnore
119 | */
120 | public function getUserInfo()
121 | {
122 | return '';
123 | }
124 |
125 | /**
126 | * Not really implemented, because Cortex does not need it.
127 | *
128 | * @inheritdoc
129 | * @codeCoverageIgnore
130 | */
131 | public function getFragment()
132 | {
133 | return '';
134 | }
135 |
136 | /**
137 | * Disabled.
138 | *
139 | * @inheritdoc
140 | */
141 | public function withScheme($scheme)
142 | {
143 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
144 | }
145 |
146 | /**
147 | * Disabled.
148 | *
149 | * @inheritdoc
150 | */
151 | public function withUserInfo($user, $password = null)
152 | {
153 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
154 | }
155 |
156 | /**
157 | * Disabled.
158 | *
159 | * @inheritdoc
160 | */
161 | public function withHost($host)
162 | {
163 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
164 | }
165 |
166 | /**
167 | * Disabled.
168 | *
169 | * @inheritdoc
170 | */
171 | public function withPort($port)
172 | {
173 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
174 | }
175 |
176 | /**
177 | * Disabled.
178 | *
179 | * @inheritdoc
180 | */
181 | public function withPath($path)
182 | {
183 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
184 | }
185 |
186 | /**
187 | * Disabled.
188 | *
189 | * @inheritdoc
190 | */
191 | public function withQuery($query)
192 | {
193 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
194 | }
195 |
196 | /**
197 | * Disabled.
198 | *
199 | * @inheritdoc
200 | */
201 | public function withFragment($fragment)
202 | {
203 | throw new \BadMethodCallException(sprintf('%s is read-only.', __CLASS__));
204 | }
205 |
206 | /**
207 | * @inheritdoc
208 | */
209 | public function __toString()
210 | {
211 | $this->parsed or $this->marshallFromServer();
212 |
213 | $url = sprintf('%s:://%s/%s', $this->getScheme(), $this->getHost(), $this->getPath());
214 | $query = $this->getQuery();
215 |
216 | return $query ? "{$url}?{$query}" : $url;
217 | }
218 |
219 | /**
220 | * Parse server array to find url components.
221 | */
222 | private function marshallFromServer()
223 | {
224 | $scheme = is_ssl() ? 'https' : 'http';
225 |
226 | $host = $this->marshallHostFromServer() ? : parse_url(home_url(), PHP_URL_HOST);
227 | $host = trim((string)$host, '/');
228 |
229 | $pathArray = explode('?', $this->marshallPathFromServer(), 2);
230 | $path = trim($pathArray[0], '/');
231 |
232 | empty($path) and $path = '/';
233 |
234 | $query_string = '';
235 | if (isset($this->server['QUERY_STRING'])) {
236 | $query_string = ltrim($this->server['QUERY_STRING'], '?');
237 | }
238 |
239 | $this->storage = compact('scheme', 'host', 'path', 'query_string');
240 | $this->parsed = true;
241 | }
242 |
243 | /**
244 | * Parse server array to find url host.
245 | *
246 | * Contains code from Zend\Diactoros\ServerRequestFactory
247 | *
248 | * @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
249 | * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD
250 | * License
251 | *
252 | * @return string
253 | */
254 | private function marshallHostFromServer()
255 | {
256 | $host = isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : '';
257 | if (empty($host)) {
258 | return isset($this->server['SERVER_NAME']) ? $this->server['SERVER_NAME'] : '';
259 | }
260 |
261 | if (is_string($host) && preg_match('|\:(\d+)$|', $host, $matches)) {
262 | $host = substr($host, 0, -1 * (strlen($matches[1]) + 1));
263 | }
264 |
265 | return $host;
266 | }
267 |
268 | /**
269 | * Parse server array to find url path.
270 | *
271 | * Contains code from Zend\Diactoros\ServerRequestFactory
272 | *
273 | * @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
274 | * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD
275 | * License
276 | *
277 | * @return string
278 | */
279 | private function marshallPathFromServer()
280 | {
281 | $get = function ($key, array $values, $default = null) {
282 | return array_key_exists($key, $values) ? $values[$key] : $default;
283 | };
284 |
285 | // IIS7 with URL Rewrite: make sure we get the unencoded url
286 | // (double slash problem).
287 | $iisUrlRewritten = $get('IIS_WasUrlRewritten', $this->server);
288 | $unencodedUrl = $get('UNENCODED_URL', $this->server, '');
289 | if ('1' == $iisUrlRewritten && ! empty($unencodedUrl)) {
290 | return $unencodedUrl;
291 | }
292 |
293 | $requestUri = $get('REQUEST_URI', $this->server);
294 |
295 | // Check this first so IIS will catch.
296 | $httpXRewriteUrl = $get('HTTP_X_REWRITE_URL', $this->server);
297 | if ($httpXRewriteUrl !== null) {
298 | $requestUri = $httpXRewriteUrl;
299 | }
300 |
301 | // Check for IIS 7.0 or later with ISAPI_Rewrite
302 | $httpXOriginalUrl = $get('HTTP_X_ORIGINAL_URL', $this->server);
303 | if ($httpXOriginalUrl !== null) {
304 | $requestUri = $httpXOriginalUrl;
305 | }
306 |
307 | if ($requestUri !== null) {
308 | return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri);
309 | }
310 |
311 | $origPathInfo = $get('ORIG_PATH_INFO', $this->server);
312 |
313 | return empty($origPathInfo) ? '/' : $origPathInfo;
314 | }
315 | }
316 |
--------------------------------------------------------------------------------
/src/Cortex/Uri/UriInterface.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Uri;
12 |
13 | /**
14 | * @author Giuseppe Mazzapica
15 | * @license http://opensource.org/licenses/MIT MIT
16 | * @package Cortex
17 | */
18 | interface UriInterface
19 | {
20 | /**
21 | * @return string
22 | */
23 | public function scheme();
24 |
25 | /**
26 | * @return string
27 | */
28 | public function host();
29 |
30 | /**
31 | * @return string
32 | */
33 | public function path();
34 |
35 | /**
36 | * @return array
37 | */
38 | public function chunks();
39 |
40 | /**
41 | * @return array
42 | */
43 | public function vars();
44 | }
45 |
--------------------------------------------------------------------------------
/src/Cortex/Uri/WordPressUri.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain\Cortex\Uri;
12 |
13 | use Psr\Http\Message\UriInterface as PsrUriInterface;
14 |
15 | /**
16 | * @author Giuseppe Mazzapica
17 | * @license http://opensource.org/licenses/MIT MIT
18 | * @package Cortex
19 | */
20 | final class WordPressUri implements UriInterface
21 | {
22 | /**
23 | * @var \Psr\Http\Message\UriInterface
24 | */
25 | private $uri;
26 |
27 | /**
28 | * WordPressUri constructor.
29 | *
30 | * @param \Psr\Http\Message\UriInterface $uri
31 | */
32 | public function __construct(PsrUriInterface $uri)
33 | {
34 | $this->uri = $uri;
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function scheme()
41 | {
42 | return $this->uri->getScheme();
43 | }
44 |
45 | /**
46 | * @inheritdoc
47 | */
48 | public function host()
49 | {
50 | return $this->uri->getHost();
51 | }
52 |
53 | /**
54 | * @inheritdoc
55 | */
56 | public function chunks()
57 | {
58 | $path = $this->path();
59 |
60 | return $path === '/' ? [] : explode('/', $path);
61 | }
62 |
63 | /**
64 | * @inheritdoc
65 | */
66 | public function path()
67 | {
68 | /*
69 | * If WordPress is installed in a subfolder and home url is something like
70 | * `example.com/subfolder` we need to strip down `/subfolder` from path and build a path
71 | * for route matching that is relative to home url.
72 | */
73 | $homePath = trim((string)parse_url(home_url(), PHP_URL_PATH), '/');
74 | $path = trim($this->uri->getPath(), '/');
75 | if ($homePath && strpos($path, $homePath) === 0) {
76 | $path = trim(substr($path, strlen($homePath)), '/');
77 | }
78 |
79 | return $path ? : '/';
80 | }
81 |
82 | /**
83 | * @inheritdoc
84 | */
85 | public function vars()
86 | {
87 | $vars = [];
88 | parse_str($this->uri->getQuery(), $vars);
89 |
90 | return $vars;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Routes.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Brain;
12 |
13 | use Brain\Cortex\Group\Group;
14 | use Brain\Cortex\Group\GroupCollectionInterface;
15 | use Brain\Cortex\Route\QueryRoute;
16 | use Brain\Cortex\Route\RedirectRoute;
17 | use Brain\Cortex\Route\RouteCollectionInterface;
18 |
19 | /**
20 | * @author Giuseppe Mazzapica
21 | * @license http://opensource.org/licenses/MIT MIT
22 | * @package Cortex
23 | */
24 | class Routes
25 | {
26 | /**
27 | * @param string $method
28 | */
29 | private static function checkTiming($method)
30 | {
31 | if (! Cortex::late() && ! did_action('parse_request')) {
32 | return;
33 | }
34 |
35 | $exception = new \BadMethodCallException(
36 | sprintf('%s must be called before "do_parse_request".', $method)
37 | );
38 |
39 | if (defined('WP_DEBUG') && WP_DEBUG) {
40 | throw $exception;
41 | }
42 |
43 | do_action('cortex.fail', $exception);
44 | }
45 |
46 | /**
47 | * @param string $path
48 | * @param callable|array $query
49 | * @param array $options
50 | * @return \Brain\Cortex\Route\RouteInterface
51 | */
52 | public static function add($path, $query, array $options = [])
53 | {
54 | self::checkTiming(__METHOD__);
55 |
56 | $routeObj = new QueryRoute($path, $query, $options);
57 |
58 | add_action(
59 | 'cortex.routes',
60 | function (RouteCollectionInterface $collection) use ($routeObj) {
61 | $collection->addRoute($routeObj);
62 | }
63 | );
64 |
65 | return $routeObj;
66 | }
67 |
68 | /**
69 | * @param string $path
70 | * @param string|callable $to
71 | * @param int $status
72 | * @param bool $external
73 | * @return \Brain\Cortex\Route\RedirectRoute
74 | */
75 | public static function redirect($path, $to, $status = 301, $external = false)
76 | {
77 | self::checkTiming(__METHOD__);
78 |
79 | $routeObj = new RedirectRoute($path, $to, [
80 | 'redirect_status' => $status,
81 | 'redirect_external' => $external,
82 | ]);
83 |
84 | add_action(
85 | 'cortex.routes',
86 | function (RouteCollectionInterface $collection) use ($routeObj) {
87 | $collection->addRoute($routeObj);
88 | }
89 | );
90 |
91 | return $routeObj;
92 | }
93 |
94 | /**
95 | * @param string $id
96 | * @param array $group
97 | * @return \Brain\Cortex\Group\GroupInterface
98 | */
99 | public static function group($id, array $group)
100 | {
101 | self::checkTiming(__METHOD__);
102 |
103 | $group['id'] = $id;
104 | $groupObj = new Group($group);
105 |
106 | add_action(
107 | 'cortex.groups',
108 | function (GroupCollectionInterface $collection) use ($groupObj) {
109 | $collection->addGroup($groupObj);
110 | }
111 | );
112 |
113 | return $groupObj;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------