summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2023-01-06 19:53:43 +0100
committerGravatar GitHub <noreply@github.com> 2023-01-06 19:53:43 +0100
commit8f9c4143fcc133f28db4c3f618649fb1170e33b4 (patch)
treee41532df88fa10766ce7ad729e4c8b88f616ce27
parentaf8480651dea478e2a60dc13b9ea44d364d0f7b7 (diff)
Better enclosures (#4944)
* Better enclosures #fix https://github.com/FreshRSS/FreshRSS/issues/4702 Improvement of https://github.com/FreshRSS/FreshRSS/pull/2898 * A few fixes * Better enclosure titles * Improve thumbnails * Implement thumbnail for HTML+XPath * Avoid duplicate enclosures #fix https://github.com/FreshRSS/FreshRSS/issues/1668 * Fix regex * Add basic support for media:credit And use <figure> for enclosures * Fix link encoding + simplify code * Fix some SimplePie bugs Encoding errors in enclosure links * Remove debugging syslog * Remove debugging syslog * SimplePie fix multiple RSS2 enclosures #fix https://github.com/FreshRSS/FreshRSS/issues/4974 * Improve thumbnails * Performance with yield Avoid generating all enclosures if not used * API keep providing enclosures inside content Clients are typically not showing the enclosures to the users (tested with News+, FeedMe, Readrops, Fluent Reader Lite) * Lint * Fix API output enclosure * Fix API content strcut * API tolerate enclosures without a type
-rwxr-xr-xapp/Controllers/feedController.php2
-rw-r--r--app/Models/Entry.php167
-rw-r--r--app/Models/Feed.php73
-rw-r--r--app/views/helpers/index/normal/entry_header.phtml5
-rw-r--r--app/views/index/normal.phtml2
-rw-r--r--app/views/index/reader.phtml2
-rwxr-xr-xapp/views/index/rss.phtml22
-rw-r--r--lib/SimplePie/SimplePie/Enclosure.php2
-rw-r--r--lib/SimplePie/SimplePie/Item.php25
9 files changed, 211 insertions, 89 deletions
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 319faece8..3ef3af67d 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -949,7 +949,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$this->view->htmlContent = $fullContent;
} else {
$this->view->selectorSuccess = false;
- $this->view->htmlContent = $entry->content();
+ $this->view->htmlContent = $entry->content(false);
}
} catch (Exception $e) {
$this->view->fatalError = _t('feedback.sub.feed.selector_preview.http_error');
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 47fcf3b4a..ec7629253 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -67,7 +67,9 @@ class FreshRSS_Entry extends Minz_Model {
$dao['content'] = '';
}
if (!empty($dao['thumbnail'])) {
- $dao['content'] .= '<p class="enclosure-content"><img src="' . $dao['thumbnail'] . '" alt="" /></p>';
+ $dao['attributes']['thumbnail'] = [
+ 'url' => $dao['thumbnail'],
+ ];
}
$entry = new FreshRSS_Entry(
$dao['id_feed'] ?? 0,
@@ -116,15 +118,117 @@ class FreshRSS_Entry extends Minz_Model {
return $this->authors;
}
}
- public function content(): string {
- return $this->content;
+
+ /**
+ * Basic test without ambition to catch all cases such as unquoted addresses, variants of entities, HTML comments, etc.
+ */
+ private static function containsLink(string $html, string $link): bool {
+ return preg_match('/(?P<delim>[\'"])' . preg_quote($link, '/') . '(?P=delim)/', $html) == 1;
+ }
+
+ private static function enclosureIsImage(array $enclosure): bool {
+ $elink = $enclosure['url'] ?? '';
+ $length = $enclosure['length'] ?? 0;
+ $medium = $enclosure['medium'] ?? '';
+ $mime = $enclosure['type'] ?? '';
+
+ return $elink != '' && $medium === 'image' || strpos($mime, 'image') === 0 ||
+ ($mime == '' && $length == 0 && preg_match('/[.](avif|gif|jpe?g|png|svg|webp)$/i', $elink));
}
- /** @return array<array<string,string>> */
- public function enclosures(bool $searchBodyImages = false): array {
- $results = [];
+ /**
+ * @param bool $withEnclosures Set to true to include the enclosures in the returned HTML, false otherwise.
+ * @param bool $allowDuplicateEnclosures Set to false to remove obvious enclosure duplicates (based on simple string comparison), true otherwise.
+ * @return string HTML content
+ */
+ public function content(bool $withEnclosures = true, bool $allowDuplicateEnclosures = false): string {
+ if (!$withEnclosures) {
+ return $this->content;
+ }
+
+ $content = $this->content;
+
+ $thumbnail = $this->attributes('thumbnail');
+ if (!empty($thumbnail['url'])) {
+ $elink = $thumbnail['url'];
+ if ($allowDuplicateEnclosures || !self::containsLink($content, $elink)) {
+ $content .= <<<HTML
+<figure class="enclosure">
+ <p class="enclosure-content">
+ <img class="enclosure-thumbnail" src="{$elink}" alt="" />
+ </p>
+</figure>
+HTML;
+ }
+ }
+
+ $attributeEnclosures = $this->attributes('enclosures');
+ if (empty($attributeEnclosures)) {
+ return $content;
+ }
+
+ foreach ($attributeEnclosures as $enclosure) {
+ $elink = $enclosure['url'] ?? '';
+ if ($elink == '') {
+ continue;
+ }
+ if (!$allowDuplicateEnclosures && self::containsLink($content, $elink)) {
+ continue;
+ }
+ $credit = $enclosure['credit'] ?? '';
+ $description = $enclosure['description'] ?? '';
+ $length = $enclosure['length'] ?? 0;
+ $medium = $enclosure['medium'] ?? '';
+ $mime = $enclosure['type'] ?? '';
+ $thumbnails = $enclosure['thumbnails'] ?? [];
+ $etitle = $enclosure['title'] ?? '';
+
+ $content .= '<figure class="enclosure">';
+
+ foreach ($thumbnails as $thumbnail) {
+ $content .= '<p><img class="enclosure-thumbnail" src="' . $thumbnail . '" alt="" title="' . $etitle . '" /></p>';
+ }
+
+ if (self::enclosureIsImage($enclosure)) {
+ $content .= '<p class="enclosure-content"><img src="' . $elink . '" alt="" title="' . $etitle . '" /></p>';
+ } elseif ($medium === 'audio' || strpos($mime, 'audio') === 0) {
+ $content .= '<p class="enclosure-content"><audio preload="none" src="' . $elink
+ . ($length == null ? '' : '" data-length="' . intval($length))
+ . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
+ . '" controls="controls" title="' . $etitle . '"></audio> <a download="" href="' . $elink . '">💾</a></p>';
+ } elseif ($medium === 'video' || strpos($mime, 'video') === 0) {
+ $content .= '<p class="enclosure-content"><video preload="none" src="' . $elink
+ . ($length == null ? '' : '" data-length="' . intval($length))
+ . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
+ . '" controls="controls" title="' . $etitle . '"></video> <a download="" href="' . $elink . '">💾</a></p>';
+ } else { //e.g. application, text, unknown
+ $content .= '<p class="enclosure-content"><a download="" href="' . $elink
+ . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
+ . ($medium == '' ? '' : '" data-medium="' . htmlspecialchars($medium, ENT_COMPAT, 'UTF-8'))
+ . '" title="' . $etitle . '">💾</a></p>';
+ }
+
+ if ($credit != '') {
+ $content .= '<p class="enclosure-credits">© ' . $credit . '</p>';
+ }
+ if ($description != '') {
+ $content .= '<figcaption class="enclosure-description">' . $description . '</figcaption>';
+ }
+ $content .= "</figure>\n";
+ }
+
+ return $content;
+ }
+
+ /** @return iterable<array<string,string>> */
+ public function enclosures(bool $searchBodyImages = false) {
+ $attributeEnclosures = $this->attributes('enclosures');
+ if (is_array($attributeEnclosures)) {
+ // FreshRSS 1.20.1+: The enclosures are saved as attributes
+ yield from $attributeEnclosures;
+ }
try {
- $searchEnclosures = strpos($this->content, '<p class="enclosure-content') !== false;
+ $searchEnclosures = !is_array($attributeEnclosures) && (strpos($this->content, '<p class="enclosure-content') !== false);
$searchBodyImages &= (stripos($this->content, '<img') !== false);
$xpath = null;
if ($searchEnclosures || $searchBodyImages) {
@@ -133,6 +237,7 @@ class FreshRSS_Entry extends Minz_Model {
$xpath = new DOMXpath($dom);
}
if ($searchEnclosures) {
+ // Legacy code for database entries < FreshRSS 1.20.1
$enclosures = $xpath->query('//div[@class="enclosure"]/p[@class="enclosure-content"]/*[@src]');
foreach ($enclosures as $enclosure) {
$result = [
@@ -148,7 +253,7 @@ class FreshRSS_Entry extends Minz_Model {
case 'audio': $result['medium'] = 'audio'; break;
}
}
- $results[] = $result;
+ yield Minz_Helper::htmlspecialchars_utf8($result);
}
}
if ($searchBodyImages) {
@@ -159,26 +264,31 @@ class FreshRSS_Entry extends Minz_Model {
$src = $img->getAttribute('data-src');
}
if ($src != null) {
- $results[] = [
+ $result = [
'url' => $src,
- 'alt' => $img->getAttribute('alt'),
];
+ yield Minz_Helper::htmlspecialchars_utf8($result);
}
}
}
- return $results;
} catch (Exception $ex) {
- return $results;
+ Minz_Log::debug(__METHOD__ . ' ' . $ex->getMessage());
}
}
/**
* @return array<string,string>|null
*/
- public function thumbnail() {
- foreach ($this->enclosures(true) as $enclosure) {
- if (!empty($enclosure['url']) && empty($enclosure['type'])) {
- return $enclosure;
+ public function thumbnail(bool $searchEnclosures = true) {
+ $thumbnail = $this->attributes('thumbnail');
+ if (!empty($thumbnail['url'])) {
+ return $thumbnail;
+ }
+ if ($searchEnclosures) {
+ foreach ($this->enclosures(true) as $enclosure) {
+ if (self::enclosureIsImage($enclosure)) {
+ return $enclosure;
+ }
}
}
return null;
@@ -587,7 +697,7 @@ class FreshRSS_Entry extends Minz_Model {
if ($entry) {
// l’article existe déjà en BDD, en se contente de recharger ce contenu
- $this->content = $entry->content();
+ $this->content = $entry->content(false);
} else {
try {
// The article is not yet in the database, so let’s fetch it
@@ -629,7 +739,7 @@ class FreshRSS_Entry extends Minz_Model {
'guid' => $this->guid(),
'title' => $this->title(),
'author' => $this->authors(true),
- 'content' => $this->content(),
+ 'content' => $this->content(false),
'link' => $this->link(),
'date' => $this->date(true),
'hash' => $this->hash(),
@@ -677,7 +787,6 @@ class FreshRSS_Entry extends Minz_Model {
'published' => $this->date(true),
// 'updated' => $this->date(true),
'title' => $this->title(),
- 'summary' => ['content' => $this->content()],
'canonical' => [
['href' => htmlspecialchars_decode($this->link(), ENT_QUOTES)],
],
@@ -697,13 +806,16 @@ class FreshRSS_Entry extends Minz_Model {
if ($mode === 'compat') {
$item['title'] = escapeToUnicodeAlternative($this->title(), false);
unset($item['alternate'][0]['type']);
- if (mb_strlen($this->content(), 'UTF-8') > self::API_MAX_COMPAT_CONTENT_LENGTH) {
- $item['summary']['content'] = mb_strcut($this->content(), 0, self::API_MAX_COMPAT_CONTENT_LENGTH, 'UTF-8');
- }
- } elseif ($mode === 'freshrss') {
+ $item['summary'] = [
+ 'content' => mb_strcut($this->content(true), 0, self::API_MAX_COMPAT_CONTENT_LENGTH, 'UTF-8'),
+ ];
+ } else {
+ $item['content'] = [
+ 'content' => $this->content(false),
+ ];
+ }
+ if ($mode === 'freshrss') {
$item['guid'] = $this->guid();
- unset($item['summary']);
- $item['content'] = ['content' => $this->content()];
}
if ($category != null && $mode !== 'freshrss') {
$item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($category->name(), ENT_QUOTES);
@@ -718,10 +830,11 @@ class FreshRSS_Entry extends Minz_Model {
}
}
foreach ($this->enclosures() as $enclosure) {
- if (!empty($enclosure['url']) && !empty($enclosure['type'])) {
+ if (!empty($enclosure['url'])) {
$media = [
'href' => $enclosure['url'],
- 'type' => $enclosure['type'],
+ 'type' => $enclosure['type'] ?? $enclosure['medium'] ??
+ (self::enclosureIsImage($enclosure) ? 'image' : ''),
];
if (!empty($enclosure['length'])) {
$media['length'] = intval($enclosure['length']);
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 538814370..a63c2b3ea 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -502,61 +502,46 @@ class FreshRSS_Feed extends Minz_Model {
$content = html_only_entity_decode($item->get_content());
- if ($item->get_enclosures() != null) {
- $elinks = array();
+ $attributeThumbnail = $item->get_thumbnail() ?? [];
+ if (empty($attributeThumbnail['url'])) {
+ $attributeThumbnail['url'] = '';
+ }
+
+ $attributeEnclosures = [];
+ if (!empty($item->get_enclosures())) {
foreach ($item->get_enclosures() as $enclosure) {
$elink = $enclosure->get_link();
- if ($elink != '' && empty($elinks[$elink])) {
- $content .= '<div class="enclosure">';
-
- if ($enclosure->get_title() != '') {
- $content .= '<p class="enclosure-title">' . $enclosure->get_title() . '</p>';
- }
-
- $enclosureContent = '';
- $elinks[$elink] = true;
+ if ($elink != '') {
+ $etitle = $enclosure->get_title() ?? '';
+ $credit = $enclosure->get_credit() ?? null;
+ $description = $enclosure->get_description() ?? '';
$mime = strtolower($enclosure->get_type() ?? '');
$medium = strtolower($enclosure->get_medium() ?? '');
$height = $enclosure->get_height();
$width = $enclosure->get_width();
$length = $enclosure->get_length();
- if ($medium === 'image' || strpos($mime, 'image') === 0 ||
- ($mime == '' && $length == null && ($width != 0 || $height != 0 || preg_match('/[.](avif|gif|jpe?g|png|svg|webp)$/i', $elink)))) {
- $enclosureContent .= '<p class="enclosure-content"><img src="' . $elink . '" alt="" /></p>';
- } elseif ($medium === 'audio' || strpos($mime, 'audio') === 0) {
- $enclosureContent .= '<p class="enclosure-content"><audio preload="none" src="' . $elink
- . ($length == null ? '' : '" data-length="' . intval($length))
- . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
- . '" controls="controls"></audio> <a download="" href="' . $elink . '">💾</a></p>';
- } elseif ($medium === 'video' || strpos($mime, 'video') === 0) {
- $enclosureContent .= '<p class="enclosure-content"><video preload="none" src="' . $elink
- . ($length == null ? '' : '" data-length="' . intval($length))
- . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
- . '" controls="controls"></video> <a download="" href="' . $elink . '">💾</a></p>';
- } else { //e.g. application, text, unknown
- $enclosureContent .= '<p class="enclosure-content"><a download="" href="' . $elink
- . ($mime == '' ? '' : '" data-type="' . htmlspecialchars($mime, ENT_COMPAT, 'UTF-8'))
- . ($medium == '' ? '' : '" data-medium="' . htmlspecialchars($medium, ENT_COMPAT, 'UTF-8'))
- . '">💾</a></p>';
- }
- $thumbnailContent = '';
- if ($enclosure->get_thumbnails() != null) {
+ $attributeEnclosure = [
+ 'url' => $elink,
+ ];
+ if ($etitle != '') $attributeEnclosure['title'] = $etitle;
+ if ($credit != null) $attributeEnclosure['credit'] = $credit->get_name();
+ if ($description != '') $attributeEnclosure['description'] = $description;
+ if ($mime != '') $attributeEnclosure['type'] = $mime;
+ if ($medium != '') $attributeEnclosure['medium'] = $medium;
+ if ($length != '') $attributeEnclosure['length'] = intval($length);
+ if ($height != '') $attributeEnclosure['height'] = intval($height);
+ if ($width != '') $attributeEnclosure['width'] = intval($width);
+
+ if (!empty($enclosure->get_thumbnails())) {
foreach ($enclosure->get_thumbnails() as $thumbnail) {
- if (empty($elinks[$thumbnail])) {
- $elinks[$thumbnail] = true;
- $thumbnailContent .= '<p><img class="enclosure-thumbnail" src="' . $thumbnail . '" alt="" /></p>';
+ if ($thumbnail !== $attributeThumbnail['url']) {
+ $attributeEnclosure['thumbnails'][] = $thumbnail;
}
}
}
- $content .= $thumbnailContent;
- $content .= $enclosureContent;
-
- if ($enclosure->get_description() != '') {
- $content .= '<p class="enclosure-description">' . $enclosure->get_description() . '</p>';
- }
- $content .= "</div>\n";
+ $attributeEnclosures[] = $attributeEnclosure;
}
}
}
@@ -586,6 +571,10 @@ class FreshRSS_Feed extends Minz_Model {
);
$entry->_tags($tags);
$entry->_feed($this);
+ if (!empty($attributeThumbnail['url'])) {
+ $entry->_attributes('thumbnail', $attributeThumbnail);
+ }
+ $entry->_attributes('enclosures', $attributeEnclosures);
$entry->hash(); //Must be computed before loading full content
$entry->loadCompleteContent(); // Optionally load full content for truncated feeds
diff --git a/app/views/helpers/index/normal/entry_header.phtml b/app/views/helpers/index/normal/entry_header.phtml
index 43eeb7f8a..92eacf617 100644
--- a/app/views/helpers/index/normal/entry_header.phtml
+++ b/app/views/helpers/index/normal/entry_header.phtml
@@ -42,8 +42,7 @@
?><li class="item thumbnail <?= $topline_thumbnail ?> <?= $topline_summary ? '' : 'small' ?>"><?php
$thumbnail = $this->entry->thumbnail();
if ($thumbnail != null):
- ?><img src="<?= htmlspecialchars($thumbnail['url'], ENT_COMPAT, 'UTF-8') ?>" class="item-element "<?= $lazyload ? ' loading="lazy"' : '' ?><?=
- empty($thumbnail['alt']) ? '' : ' alt="' . htmlspecialchars(strip_tags($thumbnail['alt']), ENT_COMPAT, 'UTF-8') . '"' ?> /><?php
+ ?><img src="<?= $thumbnail['url'] ?>" class="item-element "<?= $lazyload ? ' loading="lazy"' : '' ?> alt="" /><?php
endif;
?></li><?php
endif; ?>
@@ -62,7 +61,7 @@
?></span><?php
endif;
if ($topline_summary):
- ?><div class="summary"><?= trim(mb_substr(strip_tags($this->entry->content()), 0, 500, 'UTF-8')) ?></div><?php
+ ?><div class="summary"><?= trim(mb_substr(strip_tags($this->entry->content(false)), 0, 500, 'UTF-8')) ?></div><?php
endif;
?></a></li>
<?php if ($topline_date) { ?><li class="item date"><time datetime="<?= $this->entry->machineReadableDate() ?>" class="item-element"><?= $this->entry->date() ?></time>&nbsp;</li><?php } ?>
diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml
index 6f7c47677..847c307ab 100644
--- a/app/views/index/normal.phtml
+++ b/app/views/index/normal.phtml
@@ -162,7 +162,7 @@ $today = @strtotime('today');
<?php } ?>
</header>
<div class="text"><?php
- echo $lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content();
+ echo $lazyload && $hidePosts ? lazyimg($this->entry->content(true)) : $this->entry->content(true);
?></div>
<?php
$display_authors_date = FreshRSS_Context::$user_conf->show_author_date === 'f' || FreshRSS_Context::$user_conf->show_author_date === 'b';
diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml
index 5789f229b..a2ea0e989 100644
--- a/app/views/index/reader.phtml
+++ b/app/views/index/reader.phtml
@@ -136,7 +136,7 @@ $MAX_TAGS_DISPLAYED = FreshRSS_Context::$user_conf->show_tags_max;
</header>
<div class="text">
- <?= $item->content() ?>
+ <?= $item->content(true) ?>
</div>
<?php
$display_authors_date = FreshRSS_Context::$user_conf->show_author_date === 'f' || FreshRSS_Context::$user_conf->show_author_date === 'b';
diff --git a/app/views/index/rss.phtml b/app/views/index/rss.phtml
index 0b07a02f3..0b3dc7955 100755
--- a/app/views/index/rss.phtml
+++ b/app/views/index/rss.phtml
@@ -29,29 +29,41 @@ foreach ($this->entries as $item) {
$authors = $item->authors();
if (is_array($authors)) {
foreach ($authors as $author) {
- echo "\t\t\t" , '<dc:creator>', $author, '</dc:creator>', "\n";
+ echo "\t\t\t", '<dc:creator>', $author, '</dc:creator>', "\n";
}
}
$categories = $item->tags();
if (is_array($categories)) {
foreach ($categories as $category) {
- echo "\t\t\t" , '<category>', $category, '</category>', "\n";
+ echo "\t\t\t", '<category>', $category, '</category>', "\n";
}
}
+ $thumbnail = $item->thumbnail(false);
+ if (!empty($thumbnail['url'])) {
+ // https://www.rssboard.org/media-rss#media-thumbnails
+ echo "\t\t\t", '<media:thumbnail url="' . $thumbnail['url']
+ . (empty($thumbnail['width']) ? '' : '" width="' . $thumbnail['width'])
+ . (empty($thumbnail['height']) ? '' : '" height="' . $thumbnail['height'])
+ . (empty($thumbnail['time']) ? '' : '" time="' . $thumbnail['time'])
+ . '" />', "\n";
+ }
$enclosures = $item->enclosures(false);
if (is_array($enclosures)) {
foreach ($enclosures as $enclosure) {
// https://www.rssboard.org/media-rss
- echo "\t\t\t" , '<media:content url="' . $enclosure['url']
+ echo "\t\t\t", '<media:content url="' . $enclosure['url']
. (empty($enclosure['medium']) ? '' : '" medium="' . $enclosure['medium'])
. (empty($enclosure['type']) ? '' : '" type="' . $enclosure['type'])
. (empty($enclosure['length']) ? '' : '" length="' . $enclosure['length'])
- . '"></media:content>', "\n";
+ . '">'
+ . (empty($enclosure['title']) ? '' : '<media:title type="html">' . $enclosure['title'] . '</media:title>')
+ . (empty($enclosure['credit']) ? '' : '<media:credit>' . $enclosure['credit'] . '</media:credit>')
+ . '</media:content>', "\n";
}
}
?>
<description><![CDATA[<?php
- echo $item->content();
+ echo $item->content(false);
?>]]></description>
<pubDate><?= date('D, d M Y H:i:s O', $item->date(true)) ?></pubDate>
<guid isPermaLink="false"><?= $item->id() > 0 ? $item->id() : $item->guid() ?></guid>
diff --git a/lib/SimplePie/SimplePie/Enclosure.php b/lib/SimplePie/SimplePie/Enclosure.php
index cc0d038b5..04bade09f 100644
--- a/lib/SimplePie/SimplePie/Enclosure.php
+++ b/lib/SimplePie/SimplePie/Enclosure.php
@@ -627,7 +627,7 @@ class SimplePie_Enclosure
{
if ($this->link !== null)
{
- return urldecode($this->link);
+ return $this->link;
}
return null;
diff --git a/lib/SimplePie/SimplePie/Item.php b/lib/SimplePie/SimplePie/Item.php
index 2fb1d3284..1285fd536 100644
--- a/lib/SimplePie/SimplePie/Item.php
+++ b/lib/SimplePie/SimplePie/Item.php
@@ -427,7 +427,16 @@ class SimplePie_Item
{
if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
{
- $this->data['thumbnail'] = $return[0]['attribs'][''];
+ $thumbnail = $return[0]['attribs'][''];
+ if (empty($thumbnail['url']))
+ {
+ $this->data['thumbnail'] = null;
+ }
+ else
+ {
+ $thumbnail['url'] = $this->sanitize($thumbnail['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+ $this->data['thumbnail'] = $thumbnail;
+ }
}
else
{
@@ -2847,9 +2856,9 @@ class SimplePie_Item
}
}
- if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure'))
+ foreach ($this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure') ?? [] as $enclosure)
{
- if (isset($enclosure[0]['attribs']['']['url']))
+ if (isset($enclosure['attribs']['']['url']))
{
// Attributes
$bitrate = null;
@@ -2867,15 +2876,15 @@ class SimplePie_Item
$url = null;
$width = null;
- $url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0]));
+ $url = $this->sanitize($enclosure['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure));
$url = $this->feed->sanitize->https_url($url);
- if (isset($enclosure[0]['attribs']['']['type']))
+ if (isset($enclosure['attribs']['']['type']))
{
- $type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+ $type = $this->sanitize($enclosure['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
}
- if (isset($enclosure[0]['attribs']['']['length']))
+ if (isset($enclosure['attribs']['']['length']))
{
- $length = intval($enclosure[0]['attribs']['']['length']);
+ $length = intval($enclosure['attribs']['']['length']);
}
// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor