Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
d6d9481
Extract helper to get change frequency
emmadesilva Jun 28, 2024
d768a3d
Add core data parameter
emmadesilva Jun 28, 2024
8e44b81
Normalize protected legacy parameter name to use identifiers
emmadesilva Jun 28, 2024
e470ac3
Document variable
emmadesilva Jun 28, 2024
8a6b35f
Add source code spacing
emmadesilva Jun 28, 2024
7e2e498
Update RELEASE_NOTES.md
emmadesilva Jun 28, 2024
1a6c69a
Move down helper method
emmadesilva Jun 28, 2024
a69867b
Create SitemapFeatureTest.php
emmadesilva Jun 28, 2024
9492fe5
Document test abstract
emmadesilva Jun 28, 2024
cea246c
Annotate test crosslinks
emmadesilva Jun 28, 2024
3072b81
Annotate test coverage
emmadesilva Jun 28, 2024
f17fb07
Draft test method
emmadesilva Jun 28, 2024
d6b19d7
Set up broad site structure
emmadesilva Jun 28, 2024
b682739
Comment line with no effect
emmadesilva Jun 28, 2024
2d29ed8
Test sitemap is not generated when conditions are not met
emmadesilva Jun 28, 2024
1deff38
Add output assertions
emmadesilva Jun 28, 2024
d8aaa38
Remove commented code covered in main site build test
emmadesilva Jun 28, 2024
5801af2
Clean up sitemap file when done
emmadesilva Jun 28, 2024
d37aff4
Merge branch '2.x-dev' into even-smarter-sitemap-generation
emmadesilva Jun 28, 2024
cd0411b
Run test with site URL
emmadesilva Jun 28, 2024
ad69058
Build the sitemap
emmadesilva Jun 28, 2024
66ef0b4
Assert file exists
emmadesilva Jun 28, 2024
1c0d67f
Assert file equals string
emmadesilva Jun 28, 2024
9ac5acc
Document test proof intention
emmadesilva Jun 28, 2024
5e35785
Format expected XML in test source
emmadesilva Jun 28, 2024
e5fcfee
Formatting
emmadesilva Jun 28, 2024
585af3e
Add todo
emmadesilva Jun 28, 2024
d8d731d
Introduce local variable
emmadesilva Jun 28, 2024
0278fd5
Inline relevant helper method call
emmadesilva Jun 28, 2024
e592f2d
Introduce local variable
emmadesilva Jun 28, 2024
88dc5d7
Draft helper to strip dynamic data
emmadesilva Jun 28, 2024
29eba25
Clean up when done
emmadesilva Jun 28, 2024
8c52c0e
Fix dynamic data in comparison
emmadesilva Jun 28, 2024
99bc070
Set Carbon time
emmadesilva Jun 28, 2024
0d4ce00
Skip test until ready
emmadesilva Jun 28, 2024
79a7862
Apply fixes from StyleCI
StyleCIBot Jun 28, 2024
f2ee36f
Remove the sitemap `processing_time_ms` attribute as it adds no value
emmadesilva Jun 28, 2024
acdc681
Sitemap generator no longer needs to track execution time
emmadesilva Jun 28, 2024
39d91de
Remove redundant method override
emmadesilva Jun 28, 2024
0604d17
Replace ternary expression with if/else block
emmadesilva Jun 28, 2024
013657e
Refactor to extract common parts of if/else block
emmadesilva Jun 28, 2024
4a0e418
Get modified time from mockable Filesystem
emmadesilva Jun 28, 2024
dd863a9
Fix formatting
emmadesilva Jun 28, 2024
cfe58ae
Get current time from mockable Carbon
emmadesilva Jun 28, 2024
2cceb37
Revert "Replace ternary expression with if/else block"
emmadesilva Jun 28, 2024
07e7650
Revert "Skip test until ready"
emmadesilva Jun 28, 2024
7c2088f
Refactor test to replace dynamic data with static mocks
emmadesilva Jun 28, 2024
9305129
Replace qualifier with an import
emmadesilva Jun 28, 2024
2a77dc6
Clean up data passing
emmadesilva Jun 28, 2024
0dcaa42
Convert concatenation to string interpolation
emmadesilva Jun 28, 2024
19935d8
Inline local variable
emmadesilva Jun 28, 2024
1434d43
Clean up formatting
emmadesilva Jun 28, 2024
301ba73
Move up method in source
emmadesilva Jun 28, 2024
1f8a3af
Add todo to remove unreachable code
emmadesilva Jun 28, 2024
8e7969e
Unwrap 'else'
emmadesilva Jun 28, 2024
31ffc5e
Remove unreachable legacy relative sitemap link resolving
emmadesilva Jun 28, 2024
7f00211
Adjust 404 priority logic to be independent of page type
emmadesilva Jun 28, 2024
cfd5545
Move down 404 priority to last in chain to prevent it being overwritten
emmadesilva Jun 28, 2024
c5f6f3e
Expect lower 404 priority
emmadesilva Jun 28, 2024
8ae31f9
Document the priority algorithm intentions
emmadesilva Jun 28, 2024
619a28a
Merge up documentation page rules with matching semantics
emmadesilva Jun 28, 2024
ffa9161
Lower 404 page priority
emmadesilva Jun 28, 2024
aabe8b8
Create priority group for more page types
emmadesilva Jun 28, 2024
00b9407
Extract new testing helper
emmadesilva Jun 28, 2024
91dcfea
Expand the XML to make it easier to read in the test output
emmadesilva Jun 28, 2024
48cef46
Create dynamic change frequencies
emmadesilva Jun 28, 2024
eb1baab
Collapse 'if' statement with common parts
emmadesilva Jun 28, 2024
c265521
Clean up and refactor method
emmadesilva Jun 28, 2024
6c61928
Remove undocumented `sitemap.dynamic_priority` config option
emmadesilva Jun 28, 2024
8ff7247
Update RELEASE_NOTES.md
emmadesilva Jun 28, 2024
502d7ef
Update RELEASE_NOTES.md
emmadesilva Jun 28, 2024
92d1296
Clean up test code
emmadesilva Jun 28, 2024
a82680f
Clarify helper method names
emmadesilva Jun 28, 2024
c294778
Revert "Document the priority algorithm intentions"
emmadesilva Jun 28, 2024
203c9d6
Extract helper method for repeated code
emmadesilva Jun 28, 2024
8b45cd9
Add generic type annotations
emmadesilva Jun 28, 2024
04aebd1
Move up helper method declaration to match call order
emmadesilva Jun 28, 2024
29661ff
Expand PHPDoc comments
emmadesilva Jun 28, 2024
7538520
Annotate numeric string return
emmadesilva Jun 28, 2024
b8d30ba
Annotate enumerated string returns based on sitemap spec
emmadesilva Jun 28, 2024
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
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ This serves two purposes:
- Minor: Data collection files are now validated for syntax errors during discovery in https://github.com/hydephp/develop/pull/1732
- Minor: Methods in the `Includes` facade now return `HtmlString` objects instead of `string` in https://github.com/hydephp/develop/pull/1738. For more information, see below.
- Minor: `Includes::path()` and `Includes::get()` methods now normalizes paths to be basenames to match the behaviour of the other include methods in https://github.com/hydephp/develop/pull/1738. This means that nested directories are no longer supported, as you should use a data collection for that.
- Minor: The `processing_time_ms` attribute in the `sitemap.xml` file has now been removed in https://github.com/hydephp/develop/pull/1744
- Improved the sitemap data generation to be smarter and more dynamic in https://github.com/hydephp/develop/pull/1744
- The `hasFeature` method on the Hyde facade and HydeKernel now only accepts a Feature enum value instead of a string for its parameter.
- Changed how the documentation search is generated, to be an `InMemoryPage` instead of a post-build task.
- Media asset files are now copied using the new build task instead of the deprecated `BuildService::transferMediaAssets()` method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,25 @@

use Hyde\Hyde;
use SimpleXMLElement;
use Hyde\Facades\Config;
use Hyde\Pages\HtmlPage;
use Hyde\Pages\BladePage;
use Hyde\Pages\MarkdownPage;
use Hyde\Pages\MarkdownPost;
use Hyde\Facades\Filesystem;
use Hyde\Pages\InMemoryPage;
use Hyde\Support\Models\Route;
use Illuminate\Support\Carbon;
use Hyde\Pages\DocumentationPage;
use Hyde\Foundation\Facades\Routes;
use Hyde\Framework\Concerns\TracksExecutionTime;

use function blank;
use function filemtime;
use function in_array;
use function date;
use function time;
use function str_starts_with;

/**
* @see https://www.sitemaps.org/protocol.html
*/
class SitemapGenerator extends BaseXmlGenerator
{
use TracksExecutionTime;

public function generate(): static
{
Routes::all()->each(function (Route $route): void {
Expand All @@ -40,17 +36,8 @@ public function generate(): static
return $this;
}

public function getXml(): string
{
$this->xmlElement->addAttribute('processing_time_ms', $this->getFormattedProcessingTime());

return parent::getXml();
}

protected function constructBaseElement(): void
{
$this->startClock();

$this->xmlElement = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"></urlset>');
$this->xmlElement->addAttribute('generator', 'HydePHP '.Hyde::version());
}
Expand All @@ -61,62 +48,69 @@ protected function addRoute(Route $route): void

$this->addChild($urlItem, 'loc', $this->resolveRouteLink($route));
$this->addChild($urlItem, 'lastmod', $this->getLastModDate($route->getSourcePath()));
$this->addChild($urlItem, 'changefreq', 'daily');
$this->addChild($urlItem, 'changefreq', $this->generateChangeFrequency(...$this->getRouteInformation($route)));
$this->addChild($urlItem, 'priority', $this->generatePriority(...$this->getRouteInformation($route)));
}

if (Config::getBool('hyde.sitemap.dynamic_priority', true)) {
$this->addChild($urlItem, 'priority', $this->getPriority(
$route->getPageClass(), $route->getPage()->getIdentifier()
));
}
protected function resolveRouteLink(Route $route): string
{
return Hyde::url($route->getOutputPath());
}

protected function getLastModDate(string $file): string
{
return date('c', @filemtime($file) ?: time());
return date('c', @Filesystem::lastModified($file) ?: Carbon::now()->timestamp);
}

protected function getPriority(string $pageClass, string $slug): string
/**
* @param class-string<\Hyde\Pages\Concerns\HydePage> $pageClass
* @return numeric-string
*/
protected function generatePriority(string $pageClass, string $identifier): string
{
$priority = 0.5;

if (in_array($pageClass, [BladePage::class, MarkdownPage::class])) {
if (in_array($pageClass, [BladePage::class, MarkdownPage::class, DocumentationPage::class])) {
$priority = 0.9;
if ($slug === 'index') {

if ($identifier === 'index') {
$priority = 1;
}
if ($slug === '404') {
$priority = 0.5;
}
}

if ($pageClass === DocumentationPage::class) {
$priority = 0.9;
if (in_array($pageClass, [MarkdownPost::class, InMemoryPage::class, HtmlPage::class])) {
$priority = 0.75;
}

if ($pageClass === MarkdownPost::class) {
$priority = 0.75;
if ($identifier === '404') {
$priority = 0.25;
}

return (string) $priority;
}

/** @return numeric-string */
protected function getFormattedProcessingTime(): string
{
return (string) $this->getExecutionTimeInMs();
}

protected function resolveRouteLink(Route $route): string
/**
* @param class-string<\Hyde\Pages\Concerns\HydePage> $pageClass
* @return 'always'|'hourly'|'daily '|'weekly'|'monthly'|'yearly'|'never'
*/
protected function generateChangeFrequency(string $pageClass, string $identifier): string
{
$baseUrl = Config::getNullableString('hyde.url');
$frequency = 'weekly';

if (blank($baseUrl) || str_starts_with($baseUrl, 'http://localhost')) {
// While the sitemap spec requires a full URL, we rather fall back
// to using relative links instead of using localhost links.
if (in_array($pageClass, [BladePage::class, MarkdownPage::class, DocumentationPage::class])) {
$frequency = 'daily';
}

return $route->getLink();
} else {
return Hyde::url($route->getOutputPath());
if ($identifier === '404') {
$frequency = 'monthly';
}

return $frequency;
}

/** @return array{class-string<\Hyde\Pages\Concerns\HydePage>, string} */
protected function getRouteInformation(Route $route): array
{
return [$route->getPageClass(), $route->getPage()->getIdentifier()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Hyde\Framework\Testing\Feature\Commands;

use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Hyde\Testing\TestCase;

Expand All @@ -16,14 +15,33 @@ class BuildSitemapCommandTest extends TestCase
{
public function testSitemapIsGeneratedWhenConditionsAreMet()
{
$this->withSiteUrl();
config(['hyde.generate_sitemap' => true]);
config(['hyde.url' => 'https://example.com']);

$this->cleanUpWhenDone('_site/sitemap.xml');

$this->assertFileDoesNotExist(Hyde::path('_site/sitemap.xml'));

$this->artisan('build:sitemap')->assertExitCode(0);
$this->artisan('build:sitemap')
->expectsOutputToContain('Generating sitemap...')
->doesntExpectOutputToContain('Skipped')
->expectsOutputToContain(' > Created _site/sitemap.xml')
->assertExitCode(0);

$this->assertFileExists(Hyde::path('_site/sitemap.xml'));
}

public function testSitemapIsNotGeneratedWhenConditionsAreNotMet()
{
config(['hyde.url' => '']);

Filesystem::unlink('_site/sitemap.xml');
$this->assertFileDoesNotExist(Hyde::path('_site/sitemap.xml'));

$this->artisan('build:sitemap')
->expectsOutputToContain('Generating sitemap...')
->expectsOutputToContain('Skipped')
->expectsOutput(' > Cannot generate sitemap without a valid base URL')
->assertExitCode(0);

$this->assertFileDoesNotExist(Hyde::path('_site/sitemap.xml'));
}
}
157 changes: 157 additions & 0 deletions packages/framework/tests/Feature/SitemapFeatureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Testing\Feature;

use Mockery;
use Hyde\Hyde;
use Hyde\Testing\TestCase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Filesystem\Filesystem;

/**
* High level test of the sitemap generation feature.
*
* It contains a setup that covers all code paths, proving 100% coverage in actual usage.
*
* @see \Hyde\Framework\Testing\Feature\Services\SitemapServiceTest
* @see \Hyde\Framework\Testing\Feature\Commands\BuildSitemapCommandTest
*
* @covers \Hyde\Framework\Features\XmlGenerators\SitemapGenerator
* @covers \Hyde\Framework\Actions\PostBuildTasks\GenerateSitemap
* @covers \Hyde\Console\Commands\BuildSitemapCommand
*/
class SitemapFeatureTest extends TestCase
{
public function testTheSitemapFeature()
{
Carbon::setTestNow('2024-01-01 12:00:00');
$filesystem = Mockery::mock(Filesystem::class)->makePartial();
$filesystem->shouldReceive('lastModified')->andReturn(Carbon::now()->timestamp);
File::swap($filesystem);

$this->cleanUpWhenDone('_site/sitemap.xml');
$this->setUpBroadSiteStructure();
$this->withSiteUrl();

$this->artisan('build:sitemap')
->expectsOutputToContain('Created _site/sitemap.xml')
->assertExitCode(0);

$this->assertFileExists('_site/sitemap.xml');

$this->assertSameXml(
'<?xml version="1.0" encoding="UTF-8"?>'."\n{$this->stripFormatting($this->expected(Hyde::version()))}\n",
file_get_contents('_site/sitemap.xml')
);
}

protected function setUpBroadSiteStructure(): void
{
$this->file('_pages/about.md', "# About\n\nThis is the about page.");
$this->file('_pages/contact.html', '<h1>Contact</h1><p>This is the contact page.</p>');
$this->file('_posts/hello-world.md', "# Hello, World!\n\nThis is the first post.");
$this->file('_posts/second-post.md', "# Second Post\n\nThis is the second post.");
$this->file('_docs/index.md', "# Documentation\n\nThis is the documentation index.");
$this->file('_docs/installation.md', "# Installation\n\nThis is the installation guide.");
$this->file('_docs/usage.md', "# Usage\n\nThis is the usage guide.");
$this->file('_docs/404.md', "# 404\n\nThis is the 404 page.");
}

protected function expected(string $version): string
{
return <<<XML
<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9" generator="HydePHP $version">
<url>
<loc>https://example.com/contact.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.75</priority>
</url>
<url>
<loc>https://example.com/404.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>monthly</changefreq>
<priority>0.25</priority>
</url>
<url>
<loc>https://example.com/index.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://example.com/about.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://example.com/posts/hello-world.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.75</priority>
</url>
<url>
<loc>https://example.com/posts/second-post.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.75</priority>
</url>
<url>
<loc>https://example.com/docs/404.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>monthly</changefreq>
<priority>0.25</priority>
</url>
<url>
<loc>https://example.com/docs/index.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://example.com/docs/installation.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://example.com/docs/usage.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://example.com/docs/search.json</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://example.com/docs/search.html</loc>
<lastmod>2024-01-01T12:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
</urlset>
XML;
}

protected function stripFormatting(string $xml): string
{
return implode('', array_map('trim', explode("\n", $xml)));
}

protected function expandLines(string $xml): string
{
return str_replace('><', ">\n<", $xml);
}

protected function assertSameXml(string $expected, string $actual): void
{
$this->assertSame($this->expandLines($expected), $this->expandLines($actual));
}
}