Przeglądaj źródła

Keep JSON feed attachments as enclosures (#9510)

convertJsonToRss() collected item attachments into
$rssItem['attachments'], but FreshRSS_Entry::fromArray() ignores that
key, so attachments from JSON Feed and JSON dot notation feeds never
reached the generated RSS and were lost. Store them in the entry's
`enclosures` attribute instead, HTML-escaped like the other fields.

- Attachments of JSON Feed and JSON dot notation feeds (`itemAttachment`, `itemAttachmentUrl`, `itemAttachmentType`, `itemAttachmentLength`) were collected by `convertJsonToRss()` into `$rssItem['attachments']`, but `FreshRSS_Entry::fromArray()` never reads that key. So the generated RSS had no `<media:content>` for them and the enclosures were silently dropped. For example, a JSON Feed podcast item with `"attachments": [{"url": "https://example.net/1.mp3", "mime_type": "audio/mpeg"}]` showed up without its audio file.
- The attachments are now stored in the entry's `enclosures` attribute, which is what `rss.phtml` renders. The URL and type are HTML-escaped like the other fields, the length is kept only when numeric, and attachments without a URL are skipped.
- Unit test converting a JSON item with an attachment whose URL contains `&`, asserting the `<media:content url="…&amp;…" type="audio/mpeg" length="1234">` element and valid XML. It fails without the fix.
Shaurya Kesarwani 1 dzień temu
rodzic
commit
931fb41640
3 zmienionych plików z 58 dodań i 11 usunięć
  1. 1 0
      CREDITS.md
  2. 24 11
      app/Utils/dotNotationUtil.php
  3. 33 0
      tests/app/Utils/dotNotationUtilTest.php

+ 1 - 0
CREDITS.md

@@ -331,6 +331,7 @@ People are sorted by name so please keep this order.
 * [scollovati](https://github.com/scollovati): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:scollovati)
 * [Sebastian K](https://github.com/skrollme): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:skrollme)
 * [Sharawey74](https://github.com/Sharawey74): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Sharawey74)
+* [Shaurya Kesarwani](https://github.com/shaurya703): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:shaurya703)
 * [shn7798](https://github.com/shn7798): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:shn7798)
 * [Simone "roughnecks" Canaletti](https://github.com/roughnecks): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:roughnecks), [Web](https://woodpeckersnest.space/)
 * [sirideain](https://github.com/sirideain): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:sirideain)

+ 24 - 11
app/Utils/dotNotationUtil.php

@@ -194,22 +194,31 @@ final class FreshRSS_dotNotation_Util
 			$rssItem['thumbnail'] = isset($dotNotation['itemThumbnail']) ? FreshRSS_dotNotation_Util::getString($jsonItem, $dotNotation['itemThumbnail']) ?? '' : '';
 
 			//Enclosures?
+			$enclosures = [];
 			if (isset($dotNotation['itemAttachment'])) {
 				$jsonItemAttachments = FreshRSS_dotNotation_Util::get($jsonItem, $dotNotation['itemAttachment']);
-				if (is_array($jsonItemAttachments) && count($jsonItemAttachments) > 0) {
-					$rssItem['attachments'] = [];
+				if (is_array($jsonItemAttachments)) {
 					foreach ($jsonItemAttachments as $attachment) {
-						$rssAttachment = [];
-						$rssAttachment['url'] = isset($dotNotation['itemAttachmentUrl'])
-							? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentUrl'])
+						$url = isset($dotNotation['itemAttachmentUrl'])
+							? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentUrl']) ?? ''
 							: '';
-						$rssAttachment['type'] = isset($dotNotation['itemAttachmentType'])
-							? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentType'])
+						if ($url === '') {
+							continue;
+						}
+						$enclosure = ['url' => Minz_Helper::htmlspecialchars_utf8($url)];
+						$type = isset($dotNotation['itemAttachmentType'])
+							? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentType']) ?? ''
 							: '';
-						$rssAttachment['length'] = isset($dotNotation['itemAttachmentLength'])
-							? FreshRSS_dotNotation_Util::get($attachment, $dotNotation['itemAttachmentLength'])
+						if ($type !== '') {
+							$enclosure['type'] = Minz_Helper::htmlspecialchars_utf8($type);
+						}
+						$length = isset($dotNotation['itemAttachmentLength'])
+							? FreshRSS_dotNotation_Util::getString($attachment, $dotNotation['itemAttachmentLength']) ?? ''
 							: '';
-						$rssItem['attachments'][] = $rssAttachment;
+						if (is_numeric($length)) {
+							$enclosure['length'] = (int)$length;
+						}
+						$enclosures[] = $enclosure;
 					}
 				}
 			}
@@ -229,7 +238,11 @@ final class FreshRSS_dotNotation_Util
 						$rssItem[$key] = Minz_Helper::htmlspecialchars_utf8($rssItem[$key]);
 					}
 				}
-				$view->entries[] = FreshRSS_Entry::fromArray($rssItem);
+				$entry = FreshRSS_Entry::fromArray($rssItem);
+				if (!empty($enclosures)) {
+					$entry->_attribute('enclosures', $enclosures);
+				}
+				$view->entries[] = $entry;
 			}
 		}
 

+ 33 - 0
tests/app/Utils/dotNotationUtilTest.php

@@ -72,4 +72,37 @@ final class dotNotationUtilTest extends \PHPUnit\Framework\TestCase {
 		self::assertStringContainsString('<category>Science &amp; Tech</category>', $rss);
 		self::assertNotFalse(simplexml_load_string($rss), 'Output must be valid XML');
 	}
+
+	public static function testConvertJsonToRssKeepsAttachments(): void {
+		FreshRSS_Context::initSystem();
+		Minz_Translate::init('en');
+		$json = <<<json
+		{
+			"items": [
+				{
+					"title": "Episode 1",
+					"url": "https://example.net/1",
+					"attachments": [
+						{"url": "https://example.net/1.mp3?a=1&b=2", "mime_type": "audio/mpeg", "size_in_bytes": 1234}
+					]
+				}
+			]
+		}
+		json;
+		$jf = json_decode($json, true);
+		self::assertIsArray($jf);
+		$rss = FreshRSS_dotNotation_Util::convertJsonToRss($jf, 'https://example.net/', [
+			'item' => 'items',
+			'itemTitle' => 'title',
+			'itemUri' => 'url',
+			'itemAttachment' => 'attachments',
+			'itemAttachmentUrl' => 'url',
+			'itemAttachmentType' => 'mime_type',
+			'itemAttachmentLength' => 'size_in_bytes',
+		], 'Test');
+		self::assertIsString($rss);
+		self::assertStringContainsString(
+			'<media:content url="https://example.net/1.mp3?a=1&amp;b=2" type="audio/mpeg" length="1234">', $rss);
+		self::assertNotFalse(simplexml_load_string($rss), 'Output must be valid XML');
+	}
 }