Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 application/forms/FeedForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ protected function assemble(): void
),
'multiOptions' => [
'auto' => $this->translate('Determine automatically'),
'rss' => $this->translate('RSS'),
'rss' => $this->translate('RSS2.0'),
'rss1.0' => $this->translate('RSS1.0'),
'atom' => $this->translate('Atom'),
'jsonfeed' => $this->translate('JSONfeed'),
],
Expand Down
11 changes: 10 additions & 1 deletion library/Feeds/FeedReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Icinga\Module\Feeds\Parser\FeedType;
use Icinga\Module\Feeds\Parser\JsonfeedParser;
use Icinga\Module\Feeds\Parser\RSSParser;
use Icinga\Module\Feeds\Parser\RSS1Parser;
use Icinga\Module\Feeds\Parser\Result\Feed;

use Icinga\Application\Config;
Expand Down Expand Up @@ -78,7 +79,13 @@ protected function parse(string $rawResponse): ?Feed
try {
return RSSParser::parse($rawResponse);
} catch (Exception $ex) {
// Not an RSS feed
// Not an RSS 2.0 feed
}

try {
return RSS1Parser::parse($rawResponse);
} catch (Exception $ex) {
// Not an RSS 1.0 feed
}

try {
Expand All @@ -96,6 +103,8 @@ protected function parse(string $rawResponse): ?Feed
throw new Exception('Unsupported feed type or invalid data in feed');
case FeedType::RSS:
return RSSParser::parse($rawResponse);
case FeedType::RSS1:
return RSS1Parser::parse($rawResponse);
case FeedType::Atom:
return AtomParser::parse($rawResponse);
case FeedType::Jsonfeed:
Expand Down
3 changes: 3 additions & 0 deletions library/Feeds/Parser/FeedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum FeedType: int
case RSS = 1;
case Atom = 2;
case Jsonfeed = 3;
case RSS1 = 4;

/**
* display returns the string representation of the type
Expand All @@ -22,6 +23,7 @@ public function display(): string
return match ($this) {
self::Auto => 'auto',
self::RSS => 'rss',
self::RSS1 => 'rss1.0',
self::Atom => 'atom',
self::Jsonfeed => 'jsonfeed',
default => throw new Exception('Unreachable code')
Expand All @@ -36,6 +38,7 @@ public static function fromDisplay(string $display): static
return match ($display) {
'auto' => self::Auto,
'rss' => self::RSS,
'rss1.0' => self::RSS1,
'atom' => self::Atom,
'jsonfeed' => self::Jsonfeed,
default => throw new Exception('Invalid FeedType')
Expand Down
152 changes: 152 additions & 0 deletions library/Feeds/Parser/RSS1Parser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace Icinga\Module\Feeds\Parser;

use Icinga\Module\Feeds\Parser\Result\Feed;
use Icinga\Module\Feeds\Parser\Result\FeedItem;

use SimpleXMLElement;
use Exception;
use DateTime;
use DateTimeInterface;

/**
* RSS1Parser is used to parse RSS 1.0 feeds
*/
class RSS1Parser
{
/**
* parse tries to parse the given string into a Feed object
*/
public static function parse(string $raw): Feed
{
$xmlElement = new SimpleXMLElement($raw);
$xmlElement->registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');
$xmlElement->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');

if ($xmlElement->getName() !== 'RDF') {
throw new Exception('Invalid RSS1.0-Feed');
}

$xmlElement->rewind();

return static::parseFeed($xmlElement);
}

protected static function parseFeed(SimpleXMLElement $xml): Feed
{
$feed = new Feed();

foreach ($xml->getChildren() as $elementName => $xmlItemElement) {
switch ($elementName) {
case 'title':
$feed->title = $xmlItemElement->__toString();
break;
case 'link':
$feed->link = $xmlItemElement->__toString();
break;
case 'description':
$feed->description = $xmlItemElement->__toString();
break;
case 'image':
foreach ($xmlItemElement as $imgTagName => $imgElement) {
if ($imgTagName === 'url') {
$feed->image = $imgElement->__toString();
break;
}
}
break;
}
}

$items = $xml->xpath('//rss:item');

foreach ($items as $xmlItemElement) {
$item = static::parseItem($feed, $xmlItemElement);
$feed->addItem($item);
}

return $feed;
}

protected static function parseDateTime(string $dateStr): ?DateTime
{
$datetime = DateTime::createFromFormat(
DateTimeInterface::RSS,
$dateStr,
);

// <dc:date> should be ISO 8601: https://web.resource.org/rss/1.0/modules/dc/
if ($datetime === false) {
$datetime = DateTime::createFromFormat(
DateTimeInterface::ISO8601,
$dateStr,
);
}

// We probably don't need this, but let's keep it just in case
if ($datetime === false) {
$datetime = DateTime::createFromFormat(
DateTimeInterface::RFC822,
$dateStr,
);
}

if ($datetime === false) {
$datetime = DateTime::createFromFormat(
DateTimeInterface::RFC7231,
$dateStr,
);
}

if ($datetime === false) {
try {
$datetime = new DateTime($dateStr);
} catch (Exception $ex) {
// NOTE: Nothing to do here, but be ultimately failed to parse
// the time
$datetime = false;
}
}

if ($datetime === false) {
return null;
}

return $datetime;
}

protected static function parseItem(Feed $feed, SimpleXMLElement $xml): FeedItem
{
$item = new FeedItem();
$item->feed = $feed;

foreach ($xml->children() as $elementName => $xmlItemElement) {
switch ($elementName) {
case 'title':
$item->title = $xmlItemElement->__toString();
break;
case 'link':
$item->link = $xmlItemElement->__toString();
break;
case 'description':
$item->description = $xmlItemElement->__toString();
break;
}
}

foreach ($xml->children('dc', true) as $elementName => $xmlItemElement) {
switch ($elementName) {
case 'date':
$dateString = $xmlItemElement->__toString();
$item->date = static::parseDateTime($dateString);
break;
case 'creator':
$item->creator = $xmlItemElement->__toString();
break;
}
}

return $item;
}
}
4 changes: 2 additions & 2 deletions library/Feeds/Parser/RSSParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use DateTimeInterface;

/**
* RSSParser is used to parse RSS feeds
* RSSParser is used to parse RSS 2.0 feeds
*/
class RSSParser
{
Expand All @@ -24,7 +24,7 @@ public static function parse(string $raw): Feed
$xmlElement = new SimpleXMLElement($raw);

if ($xmlElement->getName() !== 'rss') {
throw new Exception('Invalid RSS-Feed');
throw new Exception('Invalid RSS2.0-Feed');
}

$xmlElement->rewind();
Expand Down