diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0d33c16284d..50f03d967ae 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -18,6 +18,8 @@ This serves two purposes: ### Changed - When a navigation group is set in front matter, it will now be used regardless of the subdirectory configuration in https://github.com/hydephp/develop/pull/1703 (fixes https://github.com/hydephp/develop/issues/1515) - Use late static bindings to support overriding data collections file finding in https://github.com/hydephp/develop/pull/1717 (fixes https://github.com/hydephp/develop/issues/1716) +- Method `Hyde::hasSiteUrl()` now returns false if the site URL is for localhost in https://github.com/hydephp/develop/pull/1726 +- Method `Hyde::url()` will now return a relative URL instead of throwing an exception when supplied a path even if the site URL is not set in https://github.com/hydephp/develop/pull/1726 ### Deprecated - for soon-to-be removed features. @@ -32,3 +34,17 @@ This serves two purposes: ### Security - in case of vulnerabilities. + +### Extra information + +This release contains changes to how HydePHP behaves when a site URL is not set by the user. + +These changes are made to reduce the chance of the default `localhost` value showing up in production environments. + +Most notably, HydePHP now considers that default site URL `localhost` to mean that a site URL is not set, as the user has not set it. +This means that things like automatic canonical URLs will not be added, as Hyde won't know how to make them without a site URL. +The previous behaviour was that Hyde used `localhost` in canonical URLs, which is never useful in production environments. + +For this reason, we felt it worth it to make this change in a minor release, as it has a such large benefit for sites. + +You can read more about the details and design decisions of this change in the following pull request https://github.com/hydephp/develop/pull/1726. diff --git a/packages/framework/src/Foundation/Kernel/Hyperlinks.php b/packages/framework/src/Foundation/Kernel/Hyperlinks.php index 98b2acdcac8..9cec5ca989c 100644 --- a/packages/framework/src/Foundation/Kernel/Hyperlinks.php +++ b/packages/framework/src/Foundation/Kernel/Hyperlinks.php @@ -126,10 +126,14 @@ public function asset(string $name, bool $preferQualifiedUrl = false): string /** * Check if a site base URL has been set in config (or .env). + * + * The default value is `http://localhost`, which is not considered a valid site URL. */ public function hasSiteUrl(): bool { - return ! blank(Config::getNullableString('hyde.url')); + $value = Config::getNullableString('hyde.url'); + + return ! blank($value) && $value !== 'http://localhost'; } /** @@ -147,6 +151,13 @@ public function url(string $path = ''): string return rtrim(rtrim(Config::getString('hyde.url'), '/')."/$path", '/'); } + // Since v1.7.0, we return the relative path even if the base URL is not set, + // as this is more likely to be the desired behavior the user's expecting. + if (! blank($path)) { + return $path; + } + + // User is trying to get the base URL, but it's not set throw new BaseUrlNotSetException(); } diff --git a/packages/framework/src/Framework/Actions/PostBuildTasks/GenerateSitemap.php b/packages/framework/src/Framework/Actions/PostBuildTasks/GenerateSitemap.php index e2e15c55809..4409ede2934 100644 --- a/packages/framework/src/Framework/Actions/PostBuildTasks/GenerateSitemap.php +++ b/packages/framework/src/Framework/Actions/PostBuildTasks/GenerateSitemap.php @@ -9,7 +9,6 @@ use Hyde\Framework\Concerns\InteractsWithDirectories; use Hyde\Framework\Features\XmlGenerators\SitemapGenerator; -use function blank; use function file_put_contents; class GenerateSitemap extends PostBuildTask @@ -22,7 +21,7 @@ class GenerateSitemap extends PostBuildTask public function handle(): void { - if (blank(Hyde::url()) || str_starts_with(Hyde::url(), 'http://localhost')) { + if (! Hyde::hasSiteUrl()) { $this->skip('Cannot generate sitemap without a valid base URL'); } diff --git a/packages/framework/tests/Feature/Foundation/HyperlinksTest.php b/packages/framework/tests/Feature/Foundation/HyperlinksTest.php index 10eb5db949d..bd811f612e1 100644 --- a/packages/framework/tests/Feature/Foundation/HyperlinksTest.php +++ b/packages/framework/tests/Feature/Foundation/HyperlinksTest.php @@ -56,7 +56,8 @@ public function testAssetHelperResolvesPathsForNestedPages() public function testAssetHelperReturnsQualifiedAbsoluteUriWhenRequestedAndSiteHasBaseUrl() { - $this->assertSame('http://localhost/media/test.jpg', $this->class->asset('test.jpg', true)); + config(['hyde.url' => 'https://example.org']); + $this->assertSame('https://example.org/media/test.jpg', $this->class->asset('test.jpg', true)); } public function testAssetHelperReturnsDefaultRelativePathWhenQualifiedAbsoluteUriIsRequestedButSiteHasNoBaseUrl() @@ -65,11 +66,22 @@ public function testAssetHelperReturnsDefaultRelativePathWhenQualifiedAbsoluteUr $this->assertSame('media/test.jpg', $this->class->asset('test.jpg', true)); } + public function testAssetHelperReturnsDefaultRelativePathWhenQualifiedAbsoluteUriIsRequestedButSiteBaseUrlIsLocalhost() + { + $this->assertSame('media/test.jpg', $this->class->asset('test.jpg', true)); + } + public function testAssetHelperReturnsInputWhenQualifiedAbsoluteUriIsRequestedButImageIsAlreadyQualified() { $this->assertSame('http://localhost/media/test.jpg', $this->class->asset('http://localhost/media/test.jpg', true)); } + public function testAssetHelperReturnsInputWhenQualifiedAbsoluteUriIsRequestedButImageIsAlreadyQualifiedRegardlessOfMatchingTheConfiguredUrl() + { + config(['hyde.url' => 'https://example.org']); + $this->assertSame('http://localhost/media/test.jpg', $this->class->asset('http://localhost/media/test.jpg', true)); + } + public function testAssetHelperUsesConfiguredMediaDirectory() { Hyde::setMediaDirectory('_assets'); diff --git a/packages/framework/tests/Feature/HelpersTest.php b/packages/framework/tests/Feature/HelpersTest.php index a28e49e9497..733a832e7ec 100644 --- a/packages/framework/tests/Feature/HelpersTest.php +++ b/packages/framework/tests/Feature/HelpersTest.php @@ -77,7 +77,14 @@ public function testAssetFunction() public function testAssetFunctionWithQualifiedUrl() { $this->assertSame(Hyde::asset('foo', true), asset('foo', true)); - $this->assertSame('http://localhost/media/foo', asset('foo', true)); + $this->assertSame('media/foo', asset('foo', true)); + } + + /** @covers ::asset */ + public function testAssetFunctionWithQualifiedUrlAndSetBaseUrl() + { + $this->app['config']->set(['hyde.url' => 'https://example.com']); + $this->assertSame('https://example.com/media/foo', asset('foo', true)); } /** @covers ::asset */ @@ -94,6 +101,13 @@ public function testAssetFunctionWithQualifiedUrlAndNoBaseUrl() $this->assertSame('media/foo', asset('foo', true)); } + /** @covers ::asset */ + public function testAssetFunctionWithQualifiedUrlAndLocalhostBaseUrl() + { + $this->app['config']->set(['hyde.url' => 'http://localhost']); + $this->assertSame('media/foo', asset('foo', true)); + } + /** @covers ::asset */ public function testAssetFunctionFromNestedPage() { @@ -145,17 +159,39 @@ public function testUrlFunction() /** @covers ::url */ public function testUrlFunctionWithBaseUrl() + { + $this->app['config']->set(['hyde.url' => 'https://example.com']); + $this->assertSame('https://example.com/foo', url('foo')); + } + + /** @covers ::url */ + public function testUrlFunctionWithLocalhostBaseUrl() { $this->app['config']->set(['hyde.url' => 'http://localhost']); - $this->assertSame('http://localhost/foo', url('foo')); + $this->assertSame('foo', url('foo')); } /** @covers ::url */ public function testUrlFunctionWithoutBaseUrl() { $this->app['config']->set(['hyde.url' => null]); + $this->assertSame('foo', url('foo')); + } + + /** @covers ::url */ + public function testUrlFunctionWithoutBaseUrlOrPath() + { + $this->app['config']->set(['hyde.url' => null]); + $this->expectException(\Hyde\Framework\Exceptions\BaseUrlNotSetException::class); + $this->assertNull(url()); + } + + /** @covers ::url */ + public function testUrlFunctionWithLocalhostBaseUrlButNoPath() + { + $this->app['config']->set(['hyde.url' => 'http://localhost']); $this->expectException(\Hyde\Framework\Exceptions\BaseUrlNotSetException::class); - $this->assertNull(url('foo')); + $this->assertNull(url()); } /** @covers ::url */ diff --git a/packages/framework/tests/Feature/SitesWithoutBaseUrlAreHandledGracefullyTest.php b/packages/framework/tests/Feature/SitesWithoutBaseUrlAreHandledGracefullyTest.php new file mode 100644 index 00000000000..cd4e9373605 --- /dev/null +++ b/packages/framework/tests/Feature/SitesWithoutBaseUrlAreHandledGracefullyTest.php @@ -0,0 +1,77 @@ + null]); + + $this->assertStringNotContainsString('http://localhost', $this->getHtml($class)); + } + + /** @dataProvider pageClassProvider */ + public function testLocalhostLinksAreNotAddedToCompiledHtmlWhenBaseUrlIsNotSet(string $class) + { + config(['hyde.url' => '']); + + $this->assertStringNotContainsString('http://localhost', $this->getHtml($class)); + } + + /** @dataProvider pageClassProvider */ + public function testLocalhostLinksAreNotAddedToCompiledHtmlWhenBaseUrlIsSetToLocalhost(string $class) + { + config(['hyde.url' => 'http://localhost']); + + $this->assertStringNotContainsString('http://localhost', $this->getHtml($class)); + } + + /** @dataProvider pageClassProvider */ + public function testSiteUrlLinksAreAddedToCompiledHtmlWhenBaseUrlIsSetToValidUrl(string $class) + { + config(['hyde.url' => 'https://example.com']); + + $this->assertStringNotContainsString('http://localhost', $this->getHtml($class)); + } + + protected function getHtml(string $class): string + { + $page = new $class('foo'); + + Hyde::shareViewData($page); + + return $page->compile(); + } +} diff --git a/packages/framework/tests/Feature/Views/MetadataViewTest.php b/packages/framework/tests/Feature/Views/MetadataViewTest.php index 199759644d4..54ba018d2d4 100644 --- a/packages/framework/tests/Feature/Views/MetadataViewTest.php +++ b/packages/framework/tests/Feature/Views/MetadataViewTest.php @@ -25,7 +25,7 @@ protected function setUp(): void { parent::setUp(); - config(['hyde.url' => 'http://localhost']); + config(['hyde.url' => 'https://example.com']); config(['hyde.enable_cache_busting' => false]); } @@ -79,7 +79,7 @@ protected function getDefaultTags(): array '', '', '', - '', + '', '', '', ]; @@ -93,7 +93,7 @@ public function testMetadataTagsInEmptyBladePage() $assertions = $this->assertSee('test', array_merge($this->getDefaultTags(), [ 'HydePHP - Test', '', - '', + '', '', '', ])); @@ -109,7 +109,7 @@ public function testMetadataTagsInEmptyMarkdownPage() $assertions = $this->assertSee('test', array_merge($this->getDefaultTags(), [ 'HydePHP - Test', '', - '', + '', '', '', ])); @@ -125,7 +125,7 @@ public function testMetadataTagsInEmptyDocumentationPage() $assertions = $this->assertSee('docs/test', array_merge($this->getDefaultTags(), [ 'HydePHP - Test', '', - '', + '', '', '', ])); @@ -140,16 +140,16 @@ public function testMetadataTagsInEmptyMarkdownPost() $assertions = $this->assertSee('posts/test', array_merge($this->getDefaultTags(), [ 'HydePHP - Test', - '', + '', '', - '', + '', '', - '', + '', '', - '', + '', '', '', - '', + '', ])); $this->assertAllTagsWereCovered('posts/test', $assertions); @@ -177,25 +177,92 @@ public function testMetadataTagsInMarkdownPostWithFlatFrontMatter() $assertions = $this->assertSee('posts/test', array_merge($this->getDefaultTags(), [ 'HydePHP - My title', - '', + '', '', - '', + '', '', '', '', '', - '', + '', '', - '', + '', '', '', '', '', - '', + '', '', '', ])); $this->assertAllTagsWereCovered('posts/test', $assertions); } + + public function testCanonicalUrlTagsAreNotAddedWhenCanonicalUrlIsNotSet() + { + config(['hyde.url' => 'http://localhost']); + + $this->file('_posts/test.md', <<<'MARKDOWN' + --- + title: "My title" + description: "My description" + category: "My category" + date: "2022-01-01" + author: "Mr. Hyde" + image: image.jpg + --- + + ## Hello World + + Lorem Ipsum Dolor Amet. + MARKDOWN + ); + $this->build('_posts/test.md'); + + $assertions = $this->assertSee('posts/test', [ + '', + '', + '', + '', + '', + 'HydePHP - My title', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + + // '', + // '', + // '', + // '', + // '', + // '', + ]); + + $this->assertAllTagsWereCovered('posts/test', $assertions); + + $dontSee = [ + '