Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Http/Request/InteractsWithHeaders.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ trait InteractsWithHeaders
*
* @return array<string, string>
*/
public function headers()
public function headers(): array
{
return array_map('sanitize_text_field', $this->rawHeaders());
}
Expand Down Expand Up @@ -51,7 +51,7 @@ public function rawHeader(string $name, ?string $default = null)
*
* @return bool
*/
public function hasHeader(string $name)
public function hasHeader(string $name): bool
{
return \array_key_exists(self::canonicalHeaderName($name), $this->rawHeaders());
}
Expand All @@ -64,7 +64,7 @@ public function hasHeader(string $name)
private function rawHeaders(): array
{
if (isset($this->rest) && $this->rest instanceof WP_REST_Request) {
return array_map(static fn ($values) => implode(', ', (array) $values), (array) $this->rest->get_headers());
return array_map(static fn ($values): string => implode(', ', (array) $values), (array) $this->rest->get_headers());
}

return $this->serverHeaders();
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Router/RouteRegister.php
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ private function invokeAsReflectionFunction(callable $method): mixed
return $reflectionFunction->invoke(...$params);
}

private function processParameters($reflectionParams, array $params = []): array
private function processParameters(array $reflectionParams, array $params = []): array
{
$requestParams = [];
foreach ($reflectionParams as $param) {
Expand Down
12 changes: 10 additions & 2 deletions src/Http/Router/StaticRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,20 @@ private function routePath(RouteRegister $route): string
$prefix = trim((string) $route->getRoutePrefix(), '/');
$path = trim((string) $route->getPath(), '/');

return $prefix === '' ? $path : $prefix . '/' . $path;
return self::joinPath($prefix, $path);
}

/**
* Joins path segments with '/', skipping empty ones so a root route leaves no trailing slash.
*/
private static function joinPath(string ...$segments): string
{
return implode('/', array_filter($segments, static fn (string $segment): bool => $segment !== ''));
}

private function isRouteMatched(RouteRegister $route, string $requestPath): bool
{
$path = $this->pageName . '/' . $this->routePath($route);
$path = self::joinPath($this->pageName, $this->routePath($route));
$compiled = RoutePattern::compile($path);
$pattern = $compiled === null ? preg_quote($path, '~') : $compiled['regex'];

Expand Down
100 changes: 98 additions & 2 deletions tests/Router/StaticRoutingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -558,13 +558,109 @@ public function testRootRuleCoexistsWithOtherRoutes(): void
assertTest(isset(WpKitTestState::$rewriteRules['^landing/entries/([^/]+)/?$']), 'route rule missing next to the root rule');
}

private function makeTransport(array $routes = []): StaticRouter
public function testRootRouteMatchesTheParsedPageRoot(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
$this->setParsedRequest('landing');

do_action('template_redirect');

assertSameValue('page-home', apply_filters('the_content', 'page-'), 'root route did not match the parsed page root');
}

public function testRootRouteMatchesThePageRootOnSubdirectoryInstalls(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
WpKitTestState::$options['home'] = 'https://example.test/blog';
$_SERVER['REQUEST_URI'] = '/blog/landing';

do_action('template_redirect');

assertSameValue('page-home', apply_filters('the_content', 'page-'), 'root route did not match on a subdirectory install');
}

public function testRootRouteMatchesThePageRootFromTheRequestUri(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
$_SERVER['REQUEST_URI'] = '/landing';

do_action('template_redirect');

assertSameValue('page-home', apply_filters('the_content', 'page-'), 'root route did not match a page root without a trailing slash');
}

public function testRootRouteMatchesThePageRootFromATrailingSlashRequestUri(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
$_SERVER['REQUEST_URI'] = '/landing/?preview=1';

do_action('template_redirect');

assertSameValue('page-home', apply_filters('the_content', 'page-'), 'root route did not match a page root with a trailing slash');
}

public function testRootRouteDoesNotMatchDeeperPaths(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
$this->setParsedRequest('landing/entries/42');

do_action('template_redirect');

assertTest(!isset(WpKitTestState::$filters['the_content']), 'root route matched a deeper path');
}

public function testRootRouteDoesNotMatchAPageSharingItsNamePrefix(): void
{
$this->makeTransport(['/' => static fn () => 'home']);
$this->setParsedRequest('landing-archive');

do_action('template_redirect');

assertTest(!isset(WpKitTestState::$filters['the_content']), 'root route matched another page sharing its name prefix');
}

public function testRootRouteDoesNotShadowSiblingRoutes(): void
{
$this->makeTransport([
'/' => static fn () => 'home',
'user/{id}' => static fn ($id) => 'user ' . $id,
]);
$this->setParsedRequest('landing/user/5');

do_action('template_redirect');

assertSameValue('page-user 5', apply_filters('the_content', 'page-'), 'root route shadowed a sibling route');
}

public function testEmptyPathInsideAPrefixMatchesThePrefixRoot(): void
{
$this->makeTransport(['/' => static fn () => 'account'], 'account');
$this->setParsedRequest('landing/account');

do_action('template_redirect');

assertSameValue('page-account', apply_filters('the_content', 'page-'), 'empty path inside a prefix did not match the prefix root');
}

/**
* Registers the given GET routes, optionally under a prefix, on a static router for the landing page.
*/
private function makeTransport(array $routes = [], ?string $prefix = null): StaticRouter
{
new Router('static', 'landing', null);
foreach ($routes as $path => $action) {
(new RouteBase())->get($path, $action);
(new RouteBase())->prefix($prefix)->get($path, $action);
}

return new StaticRouter('landing', 'plugin_activate', 'plugin_deactivate');
}

/**
* Sets the parsed WP request path that the router reads from the global WP object.
*/
private function setParsedRequest(string $path): void
{
$GLOBALS['wp'] = new \WP();
$GLOBALS['wp']->request = $path;
}
}
Loading