diff --git a/CHANGELOG.md b/CHANGELOG.md index 991e725e6df..22a6bd714a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ This serves two purposes: - Updated the scroll to top button to smooth scroll to the top of the page in https://github.com/hydephp/develop/pull/2458 - Fix build command trying to use Vite in site builds if server is running in https://github.com/hydephp/develop/issues/2483 - Fixed missing content type headers for JSON and XML in the realtime compiler in https://github.com/hydephp/develop/pull/2496 +- Fixed dashboard links not resolving properly when there is a trailing slash in the URL in https://github.com/hydephp/develop/pull/2499 ### Security - in case of vulnerabilities. diff --git a/packages/realtime-compiler/resources/dashboard.blade.php b/packages/realtime-compiler/resources/dashboard.blade.php index d7bcf0fb77a..de3f37a2c52 100644 --- a/packages/realtime-compiler/resources/dashboard.blade.php +++ b/packages/realtime-compiler/resources/dashboard.blade.php @@ -268,7 +268,7 @@ @endif @endif - View + View @@ -300,7 +300,7 @@
@if(in_array($mediaFile->getExtension(), ['svg', 'png', 'jpg', 'jpeg', 'gif', 'ico'])) - {{ $mediaFile->getName() }} + {{ $mediaFile->getName() }} @else @if($dashboard::isMediaFileProbablyMinified($mediaFile->getContents())) @@ -326,7 +326,7 @@
@if($dashboard->isInteractive())
diff --git a/packages/realtime-compiler/src/Http/DashboardController.php b/packages/realtime-compiler/src/Http/DashboardController.php index fd74f487dd2..fdd3620848b 100644 --- a/packages/realtime-compiler/src/Http/DashboardController.php +++ b/packages/realtime-compiler/src/Http/DashboardController.php @@ -22,6 +22,7 @@ use Desilva\Microserve\JsonResponse; use Desilva\Microserve\HtmlResponse; use Hyde\Support\Filesystem\MediaFile; +use Hyde\Support\Models\Route; use Illuminate\Support\Facades\Process; use Hyde\Framework\Actions\StaticPageBuilder; use Hyde\Framework\Actions\AnonymousViewCompiler; @@ -135,6 +136,31 @@ public function getPageList(): array return Hyde::routes()->all(); } + public function getRoutePreviewLink(Route $route): string + { + return $this->rootRelativeLink($route->getLink()); + } + + public function getMediaPreviewLink(MediaFile $mediaFile): string + { + return $this->rootRelativeLink('media/'.$mediaFile->getIdentifier()); + } + + protected function rootRelativeLink(string $link): string + { + if (str_starts_with($link, '#') || preg_match('/^[a-z][a-z0-9+.-]*:/i', $link) || str_starts_with($link, '//')) { + return $link; + } + + if ($link === './') { + return '/'; + } + + $link = preg_replace('#^(\.\./)+#', '', $link) ?? $link; + + return '/'.ltrim($link, '/'); + } + /** @internal */ public static function bytesToHuman(int $bytes, int $precision = 2): string { diff --git a/packages/realtime-compiler/tests/RealtimeCompilerTest.php b/packages/realtime-compiler/tests/RealtimeCompilerTest.php index 01b6e81d8fc..5d01a1bb040 100644 --- a/packages/realtime-compiler/tests/RealtimeCompilerTest.php +++ b/packages/realtime-compiler/tests/RealtimeCompilerTest.php @@ -268,6 +268,33 @@ public function testPingRouteReturnsPingResponse() $this->assertSame('OK', $response->statusMessage); } + public function testDashboardLinksAreRootRelativeWhenAccessedWithTrailingSlash() + { + $dashboardEnvironment = getenv('SERVER_DASHBOARD'); + putenv('SERVER_DASHBOARD=true'); + $this->mockCompilerRoute('dashboard/'); + + Filesystem::put('_pages/foo.md', '# Hello World!'); + Filesystem::put('_media/test.css', 'body {}'); + + try { + $kernel = new HttpKernel(); + $response = $kernel->handle(new Request()); + + $this->assertInstanceOf(HtmlResponse::class, $response); + $this->assertSame(200, $response->statusCode); + $this->assertSame('OK', $response->statusMessage); + $this->assertStringContainsString('href="/foo.html"', $response->body); + $this->assertStringContainsString('href="/media/test.css"', $response->body); + $this->assertStringNotContainsString('href="foo.html"', $response->body); + $this->assertStringNotContainsString('href="media/test.css"', $response->body); + } finally { + Filesystem::unlink('_pages/foo.md'); + Filesystem::unlink('_media/test.css'); + putenv($dashboardEnvironment === false ? 'SERVER_DASHBOARD' : "SERVER_DASHBOARD=$dashboardEnvironment"); + } + } + public function testExceptionHandling() { $exception = new Exception('foo');