From 9b5787aef7b84c6d0cee01feed42b3345f0a2fce Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 29 Mar 2026 20:11:47 +0200 Subject: [PATCH] New function get_icon_url() for feed favicon Only atom:icon and a square rss2:image can be used to represent a favicon. The existing get_image_url() is more for logos / banners, not favicon. Example of real-world feeds: * https://feedpress.me/frandroid * https://www.lesnumeriques.com/rss.xml Downstream: * https://github.com/FreshRSS/FreshRSS/issues/5518 * https://github.com/FreshRSS/FreshRSS/pull/8633 --- src/SimplePie.php | 23 ++++++++- tests/Unit/SimplePieTest.php | 99 ++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/SimplePie.php b/src/SimplePie.php index 695f33e74..dd65f8385 100644 --- a/src/SimplePie.php +++ b/src/SimplePie.php @@ -3020,6 +3020,28 @@ public function get_longitude() return null; } + /** + * Get the feed icon's URL + * + * Returns favicon-like feed artwork only. + * + * Uses ``, or RSS 2.0 `` (only if square). + * + * @return string|null + */ + public function get_icon_url() + { + if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'icon')) { + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } elseif (($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) && + ($this->get_image_width() ?? -2) === ($this->get_image_height() ?? -3)) { + // Use only if the image is square, otherwise it is likely a banner and not an icon + return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); + } + + return null; + } + /** * Get the feed logo's title * @@ -3076,7 +3098,6 @@ public function get_image_url() return null; } - /** * Get the feed logo's link * diff --git a/tests/Unit/SimplePieTest.php b/tests/Unit/SimplePieTest.php index 240423e2c..7cbefe7c0 100644 --- a/tests/Unit/SimplePieTest.php +++ b/tests/Unit/SimplePieTest.php @@ -1899,6 +1899,105 @@ public function test_get_image_url(string $data, string $expected): void self::assertSame($expected, $feed->get_image_url()); } + /** + * @return array + */ + public static function getIconUrlDataProvider(): array + { + return [ + 'Test Atom 1.0 Icon' => [ +<< + http://example.com/icon.png + +XML + , + 'http://example.com/icon.png', + ], + 'Test Atom 1.0 Icon priority over RSS 2.0 image' => [ +<< + + http://example.com/icon.png + + http://example.com/image.png + 32 + 32 + + + +XML + , + 'http://example.com/icon.png', + ], + 'Test Atom 1.0 Logo is not an icon' => [ +<< + http://example.com/logo.png + +XML + , + null, + ], + 'Test RSS 2.0 Square image' => [ +<< + + + http://example.com/icon.png + 32 + 32 + + + +XML + , + 'http://example.com/icon.png', + ], + 'Test RSS 2.0 Default image not square' => [ +<< + + + http://example.com/image.png + + + +XML + , + null, + ], + 'Test RSS 2.0 Non Square Image' => [ +<< + + + http://example.com/image.png + 32 + 31 + + + +XML + , + null, + ], + ]; + } + + /** + * @dataProvider getIconUrlDataProvider + */ + public function test_get_icon_url(string $data, ?string $expected): void + { + $feed = new SimplePie(); + $feed->set_raw_data($data); + $feed->enable_cache(false); + $feed->init(); + + self::assertSame($expected, $feed->get_icon_url()); + } + /** * @return array */