diff --git a/packages/framework/src/Framework/Features/BuildTasks/PostBuildTasks/GenerateRssFeed.php b/packages/framework/src/Framework/Features/BuildTasks/PostBuildTasks/GenerateRssFeed.php
index 92d5af570a1..844001442bf 100644
--- a/packages/framework/src/Framework/Features/BuildTasks/PostBuildTasks/GenerateRssFeed.php
+++ b/packages/framework/src/Framework/Features/BuildTasks/PostBuildTasks/GenerateRssFeed.php
@@ -15,13 +15,13 @@ class GenerateRssFeed extends BuildTask
public function run(): void
{
file_put_contents(
- Hyde::sitePath(RssFeedService::getDefaultOutputFilename()),
+ Hyde::sitePath(RssFeedService::outputFilename()),
RssFeedService::generateFeed()
);
}
public function then(): void
{
- $this->createdSiteFile('_site/'.RssFeedService::getDefaultOutputFilename())->withExecutionTime();
+ $this->createdSiteFile('_site/'.RssFeedService::outputFilename())->withExecutionTime();
}
}
diff --git a/packages/framework/src/Framework/Features/Metadata/GlobalMetadataBag.php b/packages/framework/src/Framework/Features/Metadata/GlobalMetadataBag.php
index c160ba75362..29a8f16d59d 100644
--- a/packages/framework/src/Framework/Features/Metadata/GlobalMetadataBag.php
+++ b/packages/framework/src/Framework/Features/Metadata/GlobalMetadataBag.php
@@ -31,7 +31,7 @@ public static function make(): static
}
if (Features::rss()) {
- $metadataBag->add(Meta::link('alternate', Hyde::url(RssFeedService::getDefaultOutputFilename()), [
+ $metadataBag->add(Meta::link('alternate', Hyde::url(RssFeedService::outputFilename()), [
'type' => 'application/rss+xml', 'title' => RssFeedService::getDescription(),
]));
}
diff --git a/packages/framework/src/Framework/Services/RssFeedService.php b/packages/framework/src/Framework/Services/RssFeedService.php
index 4169e92c75f..4b780974187 100644
--- a/packages/framework/src/Framework/Services/RssFeedService.php
+++ b/packages/framework/src/Framework/Services/RssFeedService.php
@@ -7,10 +7,16 @@
namespace Hyde\Framework\Services;
+use function config;
+use function date;
use Exception;
+use function extension_loaded;
+use Hyde\Facades\Site;
use Hyde\Hyde;
use Hyde\Pages\MarkdownPost;
+use Hyde\Support\Helpers\XML;
use SimpleXMLElement;
+use function throw_unless;
/**
* @see \Hyde\Framework\Testing\Feature\Services\RssFeedServiceTest
@@ -21,28 +27,41 @@ class RssFeedService
{
public SimpleXMLElement $feed;
+ public static function generateFeed(): string
+ {
+ return (new static)->generate()->getXML();
+ }
+
+ public static function outputFilename(): string
+ {
+ return config('hyde.rss_filename', 'feed.xml');
+ }
+
+ public static function getDescription(): string
+ {
+ return XML::escape(config(
+ 'hyde.rss_description',
+ XML::escape(Site::name()).' RSS Feed'
+ ));
+ }
+
public function __construct()
{
- if (! extension_loaded('simplexml') || config('testing.mock_disabled_extensions', false) === true) {
- throw new Exception('The ext-simplexml extension is not installed, but is required to generate RSS feeds.');
- }
+ throw_unless(extension_loaded('simplexml'),
+ new Exception('The ext-simplexml extension is not installed, but is required to generate RSS feeds.')
+ );
$this->feed = new SimpleXMLElement('
');
$this->feed->addChild('channel');
- $this->addInitialChannelItems();
+ $this->addBaseChannelItems();
}
- /**
- * @throws \Exception
- */
public function generate(): static
{
- /** @var \Hyde\Pages\MarkdownPost $post */
- foreach (MarkdownPost::getLatestPosts() as $post) {
- $this->addItem($post);
- }
+ MarkdownPost::getLatestPosts()
+ ->each(fn (MarkdownPost $post) => $this->addItem($post));
return $this;
}
@@ -56,17 +75,18 @@ protected function addItem(MarkdownPost $post): void
{
$item = $this->feed->channel->addChild('item');
$item->addChild('title', $post->title);
- if ($post->canonicalUrl !== null) {
- $item->addChild('link', $post->canonicalUrl);
- $item->addChild('guid', $post->canonicalUrl);
- }
$item->addChild('description', $post->description);
- $this->addAdditionalItemData($item, $post);
+ $this->addDynamicItemData($item, $post);
}
- protected function addAdditionalItemData(SimpleXMLElement $item, MarkdownPost $post): void
+ protected function addDynamicItemData(SimpleXMLElement $item, MarkdownPost $post): void
{
+ if (isset($post->canonicalUrl)) {
+ $item->addChild('link', $post->canonicalUrl);
+ $item->addChild('guid', $post->canonicalUrl);
+ }
+
if (isset($post->date)) {
$item->addChild('pubDate', $post->date->dateTimeObject->format(DATE_RSS));
}
@@ -81,72 +101,37 @@ protected function addAdditionalItemData(SimpleXMLElement $item, MarkdownPost $p
if (isset($post->image)) {
$image = $item->addChild('enclosure');
- $image->addAttribute('url', Hyde::image((string) $post->image, true));
- $image->addAttribute('type', str_ends_with($post->image->getSource(), '.png') ? 'image/png' : 'image/jpeg');
- $image->addAttribute('length', (string) $post->image->getContentLength());
+ $image->addAttribute('url', Hyde::image($post->image->getSource(), true));
+ $image->addAttribute('type', $this->getImageType($post));
+ $image->addAttribute('length', $this->getImageLength($post));
}
}
- protected function addInitialChannelItems(): void
+ protected function addBaseChannelItems(): void
{
- $this->feed->channel->addChild('title', static::getTitle());
- $this->feed->channel->addChild('link', static::getLink());
+ $this->feed->channel->addChild('title', XML::escape(Site::name()));
+ $this->feed->channel->addChild('link', XML::escape(Site::url()));
$this->feed->channel->addChild('description', static::getDescription());
- $atomLink = $this->feed->channel->addChild('atom:link', namespace: 'http://www.w3.org/2005/Atom');
- $atomLink->addAttribute('href', static::getLink().'/'.static::getDefaultOutputFilename());
- $atomLink->addAttribute('rel', 'self');
- $atomLink->addAttribute('type', 'application/rss+xml');
-
- $this->addAdditionalChannelData();
- }
-
- protected function addAdditionalChannelData(): void
- {
$this->feed->channel->addChild('language', config('site.language', 'en'));
$this->feed->channel->addChild('generator', 'HydePHP '.Hyde::version());
$this->feed->channel->addChild('lastBuildDate', date(DATE_RSS));
- }
-
- protected static function xmlEscape(string $string): string
- {
- return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8');
- }
-
- public static function getDescription(): string
- {
- return static::xmlEscape(
- config(
- 'hyde.rss_description',
- static::getTitle().' RSS Feed'
- )
- );
- }
- public static function getTitle(): string
- {
- return static::xmlEscape(
- config('site.name', 'HydePHP')
- );
- }
-
- public static function getLink(): string
- {
- return static::xmlEscape(
- rtrim(
- config('site.url') ?? 'http://localhost',
- '/'
- )
- );
+ $atomLink = $this->feed->channel->addChild('atom:link', namespace: 'http://www.w3.org/2005/Atom');
+ $atomLink->addAttribute('href', XML::escape(Hyde::url(static::outputFilename())));
+ $atomLink->addAttribute('rel', 'self');
+ $atomLink->addAttribute('type', 'application/rss+xml');
}
- public static function getDefaultOutputFilename(): string
+ protected function getImageType(MarkdownPost $post): string
{
- return config('hyde.rss_filename', 'feed.xml');
+ /** @todo Add support for more types */
+ return str_ends_with($post->image->getSource(), '.png') ? 'image/png' : 'image/jpeg';
}
- public static function generateFeed(): string
+ protected function getImageLength(MarkdownPost $post): string
{
- return (new static)->generate()->getXML();
+ /** @todo We might want to add a build warning if the length is zero */
+ return (string) $post->image->getContentLength();
}
}
diff --git a/packages/framework/src/Framework/Services/SitemapService.php b/packages/framework/src/Framework/Services/SitemapService.php
index f66e5d93de2..5cd7dda48c5 100644
--- a/packages/framework/src/Framework/Services/SitemapService.php
+++ b/packages/framework/src/Framework/Services/SitemapService.php
@@ -1,19 +1,28 @@
generate()->getXML();
+ }
public function __construct()
{
- if (! extension_loaded('simplexml') || config('testing.mock_disabled_extensions', false) === true) {
- throw new Exception('The ext-simplexml extension is not installed, but is required to generate RSS feeds.');
- }
+ throw_unless(extension_loaded('simplexml'),
+ new Exception('The ext-simplexml extension is not installed, but is required to generate sitemaps.')
+ );
- $this->time_start = microtime(true);
+ $this->timeStart = microtime(true);
$this->xmlElement = new SimpleXMLElement('');
$this->xmlElement->addAttribute('generator', 'HydePHP '.Hyde::version());
@@ -39,7 +53,7 @@ public function __construct()
public function generate(): static
{
- \Hyde\Facades\Route::all()->each(function ($route) {
+ Route::all()->each(function (Route $route): void {
$this->addRoute($route);
});
@@ -48,7 +62,7 @@ public function generate(): static
public function getXML(): string
{
- $this->xmlElement->addAttribute('processing_time_ms', (string) round((microtime(true) - $this->time_start) * 1000, 2));
+ $this->xmlElement->addAttribute('processing_time_ms', $this->getFormattedProcessingTime());
return (string) $this->xmlElement->asXML();
}
@@ -56,19 +70,21 @@ public function getXML(): string
public function addRoute(Route $route): void
{
$urlItem = $this->xmlElement->addChild('url');
- $urlItem->addChild('loc', htmlentities(Hyde::url($route->getOutputPath())));
- $urlItem->addChild('lastmod', htmlentities($this->getLastModDate($route->getSourcePath())));
+
+ $urlItem->addChild('loc', XML::escape(Hyde::url($route->getOutputPath())));
+ $urlItem->addChild('lastmod', XML::escape($this->getLastModDate($route->getSourcePath())));
$urlItem->addChild('changefreq', 'daily');
+
if (config('hyde.sitemap.dynamic_priority', true)) {
- $urlItem->addChild('priority', $this->getPriority($route->getPageClass(), $route->getPage()->getIdentifier()));
+ $urlItem->addChild('priority', $this->getPriority(
+ $route->getPageClass(), $route->getPage()->getIdentifier()
+ ));
}
}
protected function getLastModDate(string $file): string
{
- return date('c', filemtime(
- $file
- ));
+ return date('c', filemtime($file));
}
protected function getPriority(string $pageClass, string $slug): string
@@ -96,8 +112,8 @@ protected function getPriority(string $pageClass, string $slug): string
return (string) $priority;
}
- public static function generateSitemap(): string
+ protected function getFormattedProcessingTime(): string
{
- return (new static)->generate()->getXML();
+ return (string) round((microtime(true) - $this->timeStart) * 1000, 2);
}
}
diff --git a/packages/framework/src/Support/Helpers/XML.php b/packages/framework/src/Support/Helpers/XML.php
new file mode 100644
index 00000000000..5097bff88ec
--- /dev/null
+++ b/packages/framework/src/Support/Helpers/XML.php
@@ -0,0 +1,18 @@
+assertInstanceOf('SimpleXMLElement', $service->feed);
}
- public function test_constructor_throws_exception_when_missing_simplexml_extension()
- {
- config(['testing.mock_disabled_extensions' => true]);
-
- $this->expectException(\Exception::class);
- $this->expectExceptionMessage('The ext-simplexml extension is not installed, but is required to generate RSS feeds.');
- new RssFeedService();
- }
-
public function test_xml_root_element_is_set_to_rss_2_0()
{
$service = new RssFeedService();
diff --git a/packages/framework/tests/Feature/Services/SitemapServiceTest.php b/packages/framework/tests/Feature/Services/SitemapServiceTest.php
index e27a09d4a96..5f0d7275eb6 100644
--- a/packages/framework/tests/Feature/Services/SitemapServiceTest.php
+++ b/packages/framework/tests/Feature/Services/SitemapServiceTest.php
@@ -30,15 +30,6 @@ public function test_service_instantiates_xml_element()
$this->assertInstanceOf('SimpleXMLElement', $service->xmlElement);
}
- public function test_constructor_throws_exception_when_missing_simplexml_extension()
- {
- config(['testing.mock_disabled_extensions' => true]);
-
- $this->expectException(\Exception::class);
- $this->expectExceptionMessage('The ext-simplexml extension is not installed, but is required to generate RSS feeds.');
- new SitemapService();
- }
-
public function test_generate_adds_default_pages_to_xml()
{
$service = new SitemapService();