Skip to content

Commit

Permalink
Merge pull request #404 from hydephp/add-sitemap
Browse files Browse the repository at this point in the history
Add sitemap.xml generation
  • Loading branch information
caendesilva authored May 18, 2022
2 parents 92f342f + 1a5d81c commit 3b7c798
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 28 deletions.
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'));
}
}

0 comments on commit 3b7c798

Please sign in to comment.