Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sitemap.xml generation #404

Merged
merged 33 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f6d6281
Add ext-simplexml as requirement
caendesilva May 18, 2022
1f66928
Create basic sitemap generator
caendesilva May 18, 2022
1e27de0
Generate the sitemap after running site build
caendesilva May 18, 2022
ea88427
Change order of page builders
caendesilva May 18, 2022
8dbffcf
Create the test class
caendesilva May 18, 2022
04ce100
Add newline after sitemap is generated
caendesilva May 18, 2022
aeb1c4a
Add lastmod property
caendesilva May 18, 2022
efeb746
Add changefreq daily
caendesilva May 18, 2022
ab01e97
Add link to the sitemap protocol
caendesilva May 18, 2022
7feb217
Add HTML entity encoding to conform with protocol
caendesilva May 18, 2022
e1b64b8
Extract get lastmod date to helper
caendesilva May 18, 2022
adcb5a0
Remove premature abstraction
caendesilva May 18, 2022
2c1c932
Add support for Markdown pages
caendesilva May 18, 2022
3166e65
Add support for blog posts
caendesilva May 18, 2022
6ab9c3f
Add support for documentation pages
caendesilva May 18, 2022
49468a9
Reorder methods and remove test code
caendesilva May 18, 2022
ce5d8ed
Add SitemapService tests
caendesilva May 18, 2022
2b08c39
Add tests covering the generated XML values
caendesilva May 18, 2022
ad88b55
Remove filemtime fallback
caendesilva May 18, 2022
46f41d6
Refactor shared code into new helper
caendesilva May 18, 2022
62e8a03
Handle file path lookup in getLastModDate helper
caendesilva May 18, 2022
cb9ecc2
Rename internal variable to match Hyde conventions
caendesilva May 18, 2022
2f67dc6
Apply fixes from StyleCI
StyleCIBot May 18, 2022
0ce34f4
Merge pull request #402 from hydephp/analysis-1bR7r5
May 18, 2022
5d870dc
Allow sitemaps to be disabled in config
caendesilva May 18, 2022
a74df7a
Merge branch 'add-sitemap' of github.com:hydephp/framework into add-s…
caendesilva May 18, 2022
43bc10f
Merge "Site URL Configuration" options
caendesilva May 18, 2022
86fe874
Change test names to use snake_case
caendesilva May 18, 2022
5316674
Remove copilot comments
caendesilva May 18, 2022
ce54fdc
Add missing test case for pretty urls option
caendesilva May 18, 2022
4e15d0e
Add tests for sitemap generation
caendesilva May 18, 2022
4ef9b3b
Apply fixes from StyleCI
StyleCIBot May 18, 2022
1a5d81c
Merge pull request #406 from hydephp/analysis-penbx4
May 18, 2022
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"laravel-zero/framework": "^9.1",
"league/commonmark": "^2.2",
"spatie/yaml-front-matter": "^2.0.7",
"torchlight/torchlight-commonmark": "^0.5.5"
"torchlight/torchlight-commonmark": "^0.5.5",
"ext-simplexml": "*"
},
"require-dev": {
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0"
Expand Down
33 changes: 12 additions & 21 deletions config/hyde.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,27 @@

/*
|--------------------------------------------------------------------------
| Site URL
| Site URL Configuration
|--------------------------------------------------------------------------
|
| If you want, you can set your site's URL here or in the .env file.
| Here are some configuration options for URL generation.
|
| The URL will then be used in meta tags to create permalinks.
| If you are serving your site from a subdirectory, you will
| need to include that in the path without a trailing slash.
| `site_url` is used to create canonical URLs and permalinks.
| `prettyUrls` will when enabled create links that do not end in .html.
| `generateSitemap` determines if a sitemap.xml file should be generated.
|
| To see the full documentation, please visit the (temporary link) below.
| https://github.com/hydephp/framework/wiki/Documentation-Page-Drafts
|
| Example: https://example.org/blog
|
*/

'site_url' => env('SITE_URL', null),

'prettyUrls' => false,

'generateSitemap' => true,

/*
|--------------------------------------------------------------------------
| Site Language
Expand Down Expand Up @@ -280,21 +286,6 @@
'smoothPageScrolling' => true,
],

/*
|--------------------------------------------------------------------------
| Pretty URLs (Links that do not end in .html)
|--------------------------------------------------------------------------
|
| Introduced in v0.25.0, you can now enable "pretty URLs". When the setting
| is enabled, generated links in the compiled HTML site are without the
| `.html` extension. Since this breaks local browsing you can leave
| the setting disabled, and instead add the `--pretty-urls` flag
| when running the `php hyde build` command for deployment.
|
*/

'prettyUrls' => false,

/*
|--------------------------------------------------------------------------
| Hyde Config Version @HydeConfigVersion 0.1.0
Expand Down
19 changes: 13 additions & 6 deletions src/Commands/HydeBuildStaticSiteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Hyde\Framework\Models\MarkdownPage;
use Hyde\Framework\Models\MarkdownPost;
use Hyde\Framework\Services\DiscoveryService;
use Hyde\Framework\Services\SitemapService;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use LaravelZero\Framework\Commands\Command;
Expand Down Expand Up @@ -66,20 +67,20 @@ public function handle(): int

$this->transferMediaAssets();

if (Features::hasBlogPosts()) {
$this->runBuildAction(MarkdownPost::class);
if (Features::hasBladePages()) {
$this->runBuildAction(BladePage::class);
}

if (Features::hasMarkdownPages()) {
$this->runBuildAction(MarkdownPage::class);
}

if (Features::hasDocumentationPages()) {
$this->runBuildAction(DocumentationPage::class);
if (Features::hasBlogPosts()) {
$this->runBuildAction(MarkdownPost::class);
}

if (Features::hasBladePages()) {
$this->runBuildAction(BladePage::class);
if (Features::hasDocumentationPages()) {
$this->runBuildAction(DocumentationPage::class);
}

$this->postBuildActions();
Expand Down Expand Up @@ -169,6 +170,12 @@ public function postBuildActions(): void
if ($this->option('run-prod')) {
$this->runNodeCommand('npm run prod', 'Building frontend assets for production!');
}

if (SitemapService::canGenerateSitemap()) {
$this->info('Generating sitemap.xml');
file_put_contents(Hyde::getSiteOutputPath('sitemap.xml'), SitemapService::generateSitemap());
$this->newLine();
}
}

/** @internal */
Expand Down
95 changes: 95 additions & 0 deletions src/Services/SitemapService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace Hyde\Framework\Services;

use Hyde\Framework\Helpers\Features;
use Hyde\Framework\Hyde;
use Hyde\Framework\Models\BladePage;
use Hyde\Framework\Models\DocumentationPage;
use Hyde\Framework\Models\MarkdownPage;
use Hyde\Framework\Models\MarkdownPost;
use SimpleXMLElement;

/**
* @see \Tests\Feature\Services\SitemapServiceTest
* @see https://www.sitemaps.org/protocol.html
*/
class SitemapService
{
public SimpleXMLElement $xmlElement;

public function __construct()
{
$this->time_start = microtime(true);

$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());
}

public function generate(): self
{
if (Features::hasBladePages()) {
$this->addPageModelUrls(
BladePage::class
);
}

if (Features::hasMarkdownPages()) {
$this->addPageModelUrls(
MarkdownPage::class
);
}

if (Features::hasBlogPosts()) {
$this->addPageModelUrls(
MarkdownPost::class,
'posts/'
);
}

if (Features::hasDocumentationPages()) {
$this->addPageModelUrls(
DocumentationPage::class,
Hyde::docsDirectory().'/'
);
}

return $this;
}

public function getXML(): string
{
$this->xmlElement->addAttribute('processing_time_ms', (string) round((microtime(true) - $this->time_start) * 1000, 2));

return $this->xmlElement->asXML();
}

public function addPageModelUrls(string $pageClass, string $routePrefix = ''): void
{
$collection = CollectionService::getSourceFileListForModel($pageClass);

foreach ($collection as $slug) {
$urlItem = $this->xmlElement->addChild('url');
$urlItem->addChild('loc', htmlentities(Hyde::uriPath(Hyde::pageLink($routePrefix.$slug.'.html'))));
$urlItem->addChild('lastmod', htmlentities($this->getLastModDate($pageClass, $slug)));
$urlItem->addChild('changefreq', 'daily');
}
}

protected function getLastModDate(string $pageClass, string $slug): string
{
return date('c', filemtime(
Hyde::path($pageClass::$sourceDirectory.DIRECTORY_SEPARATOR.$slug.$pageClass::$fileExtension)
));
}

public static function generateSitemap(): string
{
return (new static)->generate()->getXML();
}

public static function canGenerateSitemap(): bool
{
return (Hyde::uriPath() !== false) && config('hyde.generateSitemap', true);
}
}
32 changes: 32 additions & 0 deletions tests/Feature/Commands/BuildStaticSiteCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@ public function test_node_action_outputs()
->assertExitCode(0);
}

public function test_pretty_urls_option_output()
{
$this->artisan('build --pretty-urls')
->expectsOutput('Generating site with pretty URLs')
->assertExitCode(0);
}

public function test_sitemap_is_not_generated_when_conditions_are_not_met()
{
config(['hyde.site_url' => '']);
config(['hyde.generateSitemap' => false]);

unlinkIfExists(Hyde::path('_site/sitemap.xml'));
$this->artisan('build')
->assertExitCode(0);

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

public function test_sitemap_is_generated_when_conditions_are_met()
{
config(['hyde.site_url' => 'https://example.com']);
config(['hyde.generateSitemap' => true]);

unlinkIfExists(Hyde::path('_site/sitemap.xml'));
$this->artisan('build')
->expectsOutput('Generating sitemap.xml')
->assertExitCode(0);

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

/**
* Added for code coverage, deprecated as the pretty flag is deprecated.
*
Expand Down
133 changes: 133 additions & 0 deletions tests/Feature/Services/SitemapServiceTest/SitemapServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

namespace Tests\Feature\Services\SitemapServiceTest;

use Hyde\Framework\Hyde;
use Hyde\Framework\Services\SitemapService;
use Tests\TestCase;

/**
* @covers \Hyde\Framework\Services\SitemapService
*/
class SitemapServiceTest extends TestCase
{
public function test_service_instantiates_xml_element()
{
$service = new SitemapService();
$this->assertInstanceOf('SimpleXMLElement', $service->xmlElement);
}

public function test_generate_adds_default_pages_to_xml()
{
$service = new SitemapService();
$service->generate();

// Test runner has an index and 404 page, so we are using that as a baseline
$this->assertCount(2, $service->xmlElement->url);
}

public function test_generate_adds_markdown_pages_to_xml()
{
touch(Hyde::path('_pages/foo.md'));

$service = new SitemapService();
$service->generate();

$this->assertCount(3, $service->xmlElement->url);

unlink(Hyde::path('_pages/foo.md'));
}

public function test_generate_adds_markdown_posts_to_xml()
{
touch(Hyde::path('_posts/foo.md'));

$service = new SitemapService();
$service->generate();

$this->assertCount(3, $service->xmlElement->url);

unlink(Hyde::path('_posts/foo.md'));
}

public function test_generate_adds_documentation_pages_to_xml()
{
touch(Hyde::path('_docs/foo.md'));

$service = new SitemapService();
$service->generate();

$this->assertCount(3, $service->xmlElement->url);

unlink(Hyde::path('_docs/foo.md'));
}

public function test_get_xml_returns_xml_string()
{
$service = new SitemapService();
$service->generate();
$xml = $service->getXML();

$this->assertIsString($xml);
$this->assertStringStartsWith('<?xml version="1.0" encoding="UTF-8"?>', $xml);
}

public function test_generate_sitemap_shorthand_method_returns_xml_string()
{
$xml = SitemapService::generateSitemap();

$this->assertIsString($xml);
$this->assertStringStartsWith('<?xml version="1.0" encoding="UTF-8"?>', $xml);
}

public function test_can_generate_sitemap_helper_returns_true_if_hyde_has_base_url()
{
config(['hyde.site_url' => 'foo']);
$this->assertTrue(SitemapService::canGenerateSitemap());
}

public function test_can_generate_sitemap_helper_returns_false_if_hyde_does_not_have_base_url()
{
config(['hyde.site_url' => '']);
$this->assertFalse(SitemapService::canGenerateSitemap());
}

public function test_can_generate_sitemap_helper_returns_false_if_sitemaps_are_disabled_in_config()
{
config(['hyde.site_url' => 'foo']);
config(['hyde.generateSitemap' => false]);
$this->assertFalse(SitemapService::canGenerateSitemap());
}

public function test_url_item_is_generated_correctly()
{
config(['hyde.prettyUrls' => false]);
config(['hyde.site_url' => 'https://example.com']);
touch(Hyde::path('_pages/0-test.blade.php'));

$service = new SitemapService();
$service->generate();

$url = $service->xmlElement->url[0];
$this->assertEquals('https://example.com/0-test.html', $url->loc);
$this->assertEquals('daily', $url->changefreq);
$this->assertEquals(date('c'), $url->lastmod);

unlink(Hyde::path('_pages/0-test.blade.php'));
}

public function test_url_item_is_generated_with_pretty_ur_ls_if_enabled()
{
config(['hyde.prettyUrls' => true]);
config(['hyde.site_url' => 'https://example.com']);
touch(Hyde::path('_pages/0-test.blade.php'));

$service = new SitemapService();
$service->generate();

$url = $service->xmlElement->url[0];
$this->assertEquals('https://example.com/0-test', $url->loc);

unlink(Hyde::path('_pages/0-test.blade.php'));
}
}