summaryrefslogtreecommitdiff
path: root/classes/rssutils.php
diff options
context:
space:
mode:
Diffstat (limited to 'classes/rssutils.php')
-rwxr-xr-xclasses/rssutils.php349
1 files changed, 229 insertions, 120 deletions
diff --git a/classes/rssutils.php b/classes/rssutils.php
index 216792a0e..da31d4f2a 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -1,6 +1,9 @@
<?php
class RSSUtils {
- static function calculate_article_hash($article, $pluginhost) {
+ /**
+ * @param array<string, mixed> $article
+ */
+ static function calculate_article_hash(array $article, PluginHost $pluginhost): string {
$tmp = "";
$ignored_fields = [ "feed", "guid", "guid_hashed", "owner_uid", "force_catchup" ];
@@ -21,16 +24,16 @@ class RSSUtils {
}
// Strips utf8mb4 characters (i.e. emoji) for mysql
- static function strip_utf8mb4(string $str) {
+ static function strip_utf8mb4(string $str): string {
return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str);
}
- static function cleanup_feed_browser() {
+ static function cleanup_feed_browser(): void {
$pdo = Db::pdo();
$pdo->query("DELETE FROM ttrss_feedbrowser_cache");
}
- static function cleanup_feed_icons() {
+ static function cleanup_feed_icons(): void {
$pdo = Db::pdo();
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ?");
@@ -52,7 +55,10 @@ class RSSUtils {
}
}
- static function update_daemon_common(int $limit = 0, array $options = []) {
+ /**
+ * @param array<string, false|string> $options
+ */
+ static function update_daemon_common(int $limit = 0, array $options = []): int {
if (!$limit) $limit = Config::get(Config::DAEMON_FEED_LIMIT);
if (Config::get_schema_version() != Config::SCHEMA_VERSION) {
@@ -123,7 +129,8 @@ class RSSUtils {
ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON
(p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL')
WHERE
- f.owner_uid = u.id
+ f.owner_uid = u.id AND
+ u.access_level NOT IN (".sprintf("%d, %d", UserHelper::ACCESS_LEVEL_DISABLED, UserHelper::ACCESS_LEVEL_READONLY).")
$login_thresh_qpart
$update_limit_qpart
$updstart_thresh_qpart
@@ -163,7 +170,8 @@ class RSSUtils {
FROM ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON
(p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL')
WHERE
- f.owner_uid = u.id
+ f.owner_uid = u.id AND
+ u.access_level NOT IN (".sprintf("%d, %d", UserHelper::ACCESS_LEVEL_DISABLED, UserHelper::ACCESS_LEVEL_READONLY).")
AND feed_url = :feed
$login_thresh_qpart
$update_limit_qpart
@@ -270,7 +278,7 @@ class RSSUtils {
}
/** this is used when subscribing */
- static function update_basic_info(int $feed_id) {
+ static function update_basic_info(int $feed_id): void {
$feed = ORM::for_table('ttrss_feeds')
->select_many('id', 'owner_uid', 'feed_url', 'auth_pass', 'auth_login', 'title', 'site_url')
->find_one($feed_id);
@@ -352,6 +360,19 @@ class RSSUtils {
if (!$feed_language) $feed_language = mb_strtolower(get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE, $feed_obj->owner_uid));
if (!$feed_language) $feed_language = 'simple';
+ $user = ORM::for_table('ttrss_users')->find_one($feed_obj->owner_uid);
+
+ if ($user) {
+ if ($user->access_level == UserHelper::ACCESS_LEVEL_READONLY) {
+ Debug::log("error: denied update for $feed: permission denied by owner access level");
+ return false;
+ }
+ } else {
+ // this would indicate database corruption of some kind
+ Debug::log("error: owner not found for feed: $feed");
+ return false;
+ }
+
} else {
Debug::log("error: feeds table record not found for feed: $feed");
return false;
@@ -536,7 +557,7 @@ class RSSUtils {
Debug::log("language: $feed_language", Debug::LOG_VERBOSE);
Debug::log("processing feed data...", Debug::LOG_VERBOSE);
- $site_url = mb_substr(rewrite_relative_url($feed_obj->feed_url, clean($rss->get_link())), 0, 245);
+ $site_url = mb_substr(UrlHelper::rewrite_relative($feed_obj->feed_url, clean($rss->get_link())), 0, 245);
Debug::log("site_url: $site_url", Debug::LOG_VERBOSE);
Debug::log("feed_title: {$rss->get_title()}", Debug::LOG_VERBOSE);
@@ -646,7 +667,7 @@ class RSSUtils {
$entry_title = strip_tags($item->get_title());
- $entry_link = rewrite_relative_url($site_url, clean($item->get_link()));
+ $entry_link = UrlHelper::rewrite_relative($site_url, clean($item->get_link()), "a", "href");
$entry_language = mb_substr(trim($item->get_language()), 0, 2);
@@ -666,7 +687,7 @@ class RSSUtils {
}
$entry_comments = mb_substr(strip_tags($item->get_comments_url()), 0, 245);
- $num_comments = (int) $item->get_comments_count();
+ $num_comments = $item->get_comments_count();
$entry_author = strip_tags($item->get_author());
$entry_guid = mb_substr($entry_guid, 0, 245);
@@ -713,8 +734,9 @@ class RSSUtils {
},
$e, $feed);
+ // TODO: Just use FeedEnclosure (and modify it to cover whatever justified this)?
$e_item = array(
- rewrite_relative_url($site_url, $e->link),
+ UrlHelper::rewrite_relative($site_url, $e->link, "", "", $e->type),
$e->type, $e->length, $e->title, $e->width, $e->height);
// Yet another episode of "mysql utf8_general_ci is gimped"
@@ -764,13 +786,13 @@ class RSSUtils {
// dupes when the entry gets purged and reinserted again e.g.
// in the case of SLOW SLOW OMG SLOW updating feeds
+ $pdo->commit();
+
$entry_obj = ORM::for_table('ttrss_entries')
->find_one($base_entry_id)
->set('date_updated', Db::NOW())
->save();
- $pdo->commit();
-
continue;
}
@@ -825,7 +847,8 @@ class RSSUtils {
if (count($matched_filter_ids) > 0) {
$filter_objs = ORM::for_table('ttrss_filters2')
->where('owner_uid', $feed_obj->owner_uid)
- ->where_in('id', $matched_filter_ids);
+ ->where_in('id', $matched_filter_ids)
+ ->find_many();
foreach ($filter_objs as $filter_obj) {
$filter_obj->set('last_triggered', Db::NOW());
@@ -898,7 +921,7 @@ class RSSUtils {
$entry_timestamp = time();
}
- $entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp);
+ $entry_timestamp_fmt = date("Y/m/d H:i:s", $entry_timestamp);
Debug::log("date: $entry_timestamp ($entry_timestamp_fmt)", Debug::LOG_VERBOSE);
Debug::log("num_comments: $num_comments", Debug::LOG_VERBOSE);
@@ -1142,32 +1165,30 @@ class RSSUtils {
}
// check for manual tags (we have to do it here since they're loaded from filters)
-
foreach ($article_filters as $f) {
if ($f["type"] == "tag") {
+ $entry_tags = array_merge($entry_tags,
+ FeedItem_Common::normalize_categories(explode(",", $f["param"])));
+ }
+ }
- $manual_tags = array_map('trim', explode(",", mb_strtolower($f["param"])));
-
- foreach ($manual_tags as $tag) {
- array_push($entry_tags, $tag);
- }
+ // like boring tags, but filter-based
+ foreach ($article_filters as $f) {
+ if ($f["type"] == "ignore-tag") {
+ $entry_tags = array_diff($entry_tags,
+ FeedItem_Common::normalize_categories(explode(",", $f["param"])));
}
}
// Skip boring tags
-
- $boring_tags = array_map('trim',
- explode(",", mb_strtolower(
- get_pref(Prefs::BLACKLISTED_TAGS, $feed_obj->owner_uid))));
-
$entry_tags = FeedItem_Common::normalize_categories(
- array_unique(
- array_diff($entry_tags, $boring_tags)));
+ array_diff($entry_tags,
+ FeedItem_Common::normalize_categories(explode(",",
+ get_pref(Prefs::BLACKLISTED_TAGS, $feed_obj->owner_uid)))));
- Debug::log("filtered tags: " . implode(", ", $entry_tags), Debug::LOG_VERBOSE);
+ Debug::log("resulting article tags: " . implode(", ", $entry_tags), Debug::LOG_VERBOSE);
// Save article tags in the database
-
if (count($entry_tags) > 0) {
$tsth = $pdo->prepare("SELECT id FROM ttrss_tags
@@ -1250,15 +1271,21 @@ class RSSUtils {
return true;
}
- /* TODO: move to DiskCache? */
- static function cache_enclosures($enclosures, $site_url) {
+ /**
+ * TODO: move to DiskCache?
+ *
+ * @param array<int, array<string>> $enclosures An array of "enclosure arrays" [string $link, string $type, string $length, string, $title, string $width, string $height]
+ * @see RSSUtils::update_rss_feed()
+ * @see FeedEnclosure
+ */
+ static function cache_enclosures(array $enclosures, string $site_url): void {
$cache = new DiskCache("images");
if ($cache->is_writable()) {
foreach ($enclosures as $enc) {
if (preg_match("/(image|audio|video)/", $enc[1])) {
- $src = rewrite_relative_url($site_url, $enc[0]);
+ $src = UrlHelper::rewrite_relative($site_url, $enc[0]);
$local_filename = sha1($src);
@@ -1283,8 +1310,8 @@ class RSSUtils {
}
/* TODO: move to DiskCache? */
- static function cache_media_url($cache, $url, $site_url) {
- $url = rewrite_relative_url($site_url, $url);
+ static function cache_media_url(DiskCache $cache, string $url, string $site_url): void {
+ $url = UrlHelper::rewrite_relative($site_url, $url);
$local_filename = sha1($url);
Debug::log("cache_media: checking $url", Debug::LOG_VERBOSE);
@@ -1307,7 +1334,7 @@ class RSSUtils {
}
/* TODO: move to DiskCache? */
- static function cache_media($html, $site_url) {
+ static function cache_media(string $html, string $site_url): void {
$cache = new DiskCache("images");
if ($html && $cache->is_writable()) {
@@ -1336,7 +1363,7 @@ class RSSUtils {
}
}
- static function expire_error_log() {
+ static function expire_error_log(): void {
Debug::log("Removing old error log entries...");
$pdo = Db::pdo();
@@ -1350,14 +1377,16 @@ class RSSUtils {
}
}
- // deprecated; table not used
- static function expire_feed_archive() {
+ /**
+ * @deprecated table not used
+ */
+ static function expire_feed_archive(): void {
$pdo = Db::pdo();
$pdo->query("DELETE FROM ttrss_archived_feeds");
}
- static function expire_lock_files() {
+ static function expire_lock_files(): void {
Debug::log("Removing old lock files...", Debug::LOG_VERBOSE);
$num_deleted = 0;
@@ -1398,7 +1427,15 @@ class RSSUtils {
return $params;
} */
- static function get_article_filters($filters, $title, $content, $link, $author, $tags, &$matched_rules = false, &$matched_filters = false) {
+ /**
+ * @param array<int, array<string, mixed>> $filters
+ * @param array<int, string> $tags
+ * @param array<int, array<string, mixed>>|null $matched_rules
+ * @param array<int, array<string, mixed>>|null $matched_filters
+ *
+ * @return array<int, array<string, string>> An array of filter action arrays with keys "type" and "param"
+ */
+ static function get_article_filters(array $filters, string $title, string $content, string $link, string $author, array $tags, array &$matched_rules = null, array &$matched_filters = null): array {
$matches = array();
foreach ($filters as $filter) {
@@ -1482,16 +1519,26 @@ class RSSUtils {
return $matches;
}
- static function find_article_filter($filters, $filter_name) {
+ /**
+ * @param array<int, array<string, string>> $filters An array of filter action arrays with keys "type" and "param"
+ *
+ * @return array<string, string>|null A filter action array with keys "type" and "param"
+ */
+ static function find_article_filter(array $filters, string $filter_name): ?array {
foreach ($filters as $f) {
if ($f["type"] == $filter_name) {
return $f;
};
}
- return false;
+ return null;
}
- static function find_article_filters($filters, $filter_name) {
+ /**
+ * @param array<int, array<string, string>> $filters An array of filter action arrays with keys "type" and "param"
+ *
+ * @return array<int, array<string, string>> An array of filter action arrays with keys "type" and "param"
+ */
+ static function find_article_filters(array $filters, string $filter_name): array {
$results = array();
foreach ($filters as $f) {
@@ -1502,7 +1549,10 @@ class RSSUtils {
return $results;
}
- static function calculate_article_score($filters) {
+ /**
+ * @param array<int, array<string, string>> $filters An array of filter action arrays with keys "type" and "param"
+ */
+ static function calculate_article_score(array $filters): int {
$score = 0;
foreach ($filters as $f) {
@@ -1513,7 +1563,12 @@ class RSSUtils {
return $score;
}
- static function labels_contains_caption($labels, $caption) {
+ /**
+ * @param array<int, array<int, int|string>> $labels An array of label arrays like [int $feed_id, string $caption, string $fg_color, string $bg_color]
+ *
+ * @see Article::_get_labels()
+ */
+ static function labels_contains_caption(array $labels, string $caption): bool {
foreach ($labels as $label) {
if ($label[1] == $caption) {
return true;
@@ -1523,7 +1578,11 @@ class RSSUtils {
return false;
}
- static function assign_article_to_label_filters($id, $filters, $owner_uid, $article_labels) {
+ /**
+ * @param array<int, array<string, string>> $filters An array of filter action arrays with keys "type" and "param"
+ * @param array<int, array<int, int|string>> $article_labels An array of label arrays like [int $feed_id, string $caption, string $fg_color, string $bg_color]
+ */
+ static function assign_article_to_label_filters(int $id, array $filters, int $owner_uid, $article_labels): void {
foreach ($filters as $f) {
if ($f["type"] == "label") {
if (!self::labels_contains_caption($article_labels, $f["param"])) {
@@ -1533,20 +1592,20 @@ class RSSUtils {
}
}
- static function make_guid_from_title($title) {
+ static function make_guid_from_title(string $title): ?string {
return preg_replace("/[ \"\',.:;]/", "-",
mb_strtolower(strip_tags($title), 'utf-8'));
}
/* counter cache is no longer used, if called truncate leftover data */
- static function cleanup_counters_cache() {
+ static function cleanup_counters_cache(): void {
$pdo = Db::pdo();
$pdo->query("DELETE FROM ttrss_counters_cache");
$pdo->query("DELETE FROM ttrss_cat_counters_cache");
}
- static function disable_failed_feeds() {
+ static function disable_failed_feeds(): void {
if (Config::get(Config::DAEMON_UNSUCCESSFUL_DAYS_LIMIT) > 0) {
$pdo = Db::pdo();
@@ -1584,7 +1643,7 @@ class RSSUtils {
}
}
- static function housekeeping_user($owner_uid) {
+ static function housekeeping_user(int $owner_uid): void {
$tmph = new PluginHost();
UserHelper::load_user_plugins($owner_uid, $tmph);
@@ -1592,7 +1651,7 @@ class RSSUtils {
$tmph->run_hooks(PluginHost::HOOK_HOUSE_KEEPING);
}
- static function housekeeping_common() {
+ static function housekeeping_common(): void {
DiskCache::expire();
self::expire_lock_files();
@@ -1608,75 +1667,95 @@ class RSSUtils {
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING);
}
+ /**
+ * @return false|string
+ */
static function update_favicon(string $site_url, int $feed) {
$icon_file = Config::get(Config::ICONS_DIR) . "/$feed.ico";
- $favicon_url = self::get_favicon_url($site_url);
- if (!$favicon_url) {
- Debug::log("favicon: couldn't find favicon URL in $site_url", Debug::LOG_VERBOSE);
- return false;
- }
+ $favicon_urls = self::get_favicon_urls($site_url);
- // Limiting to "image" type misses those served with text/plain
- $contents = UrlHelper::fetch([
- 'url' => $favicon_url,
- 'max_size' => Config::get(Config::MAX_FAVICON_FILE_SIZE),
- //'type' => 'image',
- ]);
- if (!$contents) {
- Debug::log("favicon: fetching $favicon_url failed", Debug::LOG_VERBOSE);
+ if (count($favicon_urls) == 0) {
+ Debug::log("favicon: couldn't find any favicon URLs for $site_url", Debug::LOG_VERBOSE);
return false;
}
- // Crude image type matching.
- // Patterns gleaned from the file(1) source code.
- if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
- // 0 string \000\000\001\000 MS Windows icon resource
- //error_log("update_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
- }
- elseif (preg_match('/^GIF8/', $contents)) {
- // 0 string GIF8 GIF image data
- //error_log("update_favicon: favicon_url=$favicon_url isa GIF image");
- }
- elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
- // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
- //error_log("update_favicon: favicon_url=$favicon_url isa PNG image");
- }
- elseif (preg_match('/^\xff\xd8/', $contents)) {
- // 0 beshort 0xffd8 JPEG image data
- //error_log("update_favicon: favicon_url=$favicon_url isa JPG image");
- }
- elseif (preg_match('/^BM/', $contents)) {
- // 0 string BM PC bitmap (OS2, Windows BMP files)
- //error_log("update_favicon, favicon_url=$favicon_url isa BMP image");
- }
- else {
- //error_log("update_favicon: favicon_url=$favicon_url isa UNKNOWN type");
- Debug::log("favicon $favicon_url type is unknown (not updating)", Debug::LOG_VERBOSE);
- return false;
- }
+ // i guess we'll have to go through all of them until something looks valid...
+ foreach ($favicon_urls as $favicon_url) {
- Debug::log("favicon: saving to $icon_file", Debug::LOG_VERBOSE);
+ // Limiting to "image" type misses those served with text/plain
+ $contents = UrlHelper::fetch([
+ 'url' => $favicon_url,
+ 'max_size' => Config::get(Config::MAX_FAVICON_FILE_SIZE),
+ //'type' => 'image',
+ ]);
- $fp = @fopen($icon_file, "w");
- if (!$fp) {
- Debug::log("favicon: failed to open $icon_file for writing", Debug::LOG_VERBOSE);
- return false;
- }
+ if (!$contents) {
+ Debug::log("favicon: fetching $favicon_url failed, skipping...", Debug::LOG_VERBOSE);
+ break;
+ }
+
+ // TODO: we could use mime_conent_type() here instead of below hacks but we'll need to
+ // save every favicon to disk and go from there.
+ // also, if SVG is allowed in the future, we'll need to specifically forbid 'image/svg+xml'.
- fwrite($fp, $contents);
- fclose($fp);
- chmod($icon_file, 0644);
- clearstatcache();
+ // Crude image type matching.
+ // Patterns gleaned from the file(1) source code.
+ if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
+ // 0 string \000\000\001\000 MS Windows icon resource
+ //error_log("update_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
+ }
+ elseif (preg_match('/^GIF8/', $contents)) {
+ // 0 string GIF8 GIF image data
+ //error_log("update_favicon: favicon_url=$favicon_url isa GIF image");
+ }
+ elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
+ // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
+ //error_log("update_favicon: favicon_url=$favicon_url isa PNG image");
+ }
+ elseif (preg_match('/^\xff\xd8/', $contents)) {
+ // 0 beshort 0xffd8 JPEG image data
+ //error_log("update_favicon: favicon_url=$favicon_url isa JPG image");
+ }
+ elseif (preg_match('/^BM/', $contents)) {
+ // 0 string BM PC bitmap (OS2, Windows BMP files)
+ //error_log("update_favicon, favicon_url=$favicon_url isa BMP image");
+ }
+ else {
+ //error_log("update_favicon: favicon_url=$favicon_url isa UNKNOWN type");
+ Debug::log("favicon $favicon_url type is unknown, skipping...", Debug::LOG_VERBOSE);
+ break;
+ }
+
+ Debug::log("favicon: $favicon_url looks valid, saving to $icon_file", Debug::LOG_VERBOSE);
+
+ $fp = @fopen($icon_file, "w");
+
+ if ($fp) {
+
+ fwrite($fp, $contents);
+ fclose($fp);
+ chmod($icon_file, 0644);
+ clearstatcache();
+
+ return $icon_file;
+
+ } else {
+ Debug::log("favicon: failed to open $icon_file for writing", Debug::LOG_VERBOSE);
+ }
+ }
- return $icon_file;
+ return false;
}
- static function is_gzipped($feed_data) {
+ static function is_gzipped(string $feed_data): bool {
return strpos(substr($feed_data, 0, 3),
"\x1f" . "\x8b" . "\x08", 0) === 0;
}
+ /**
+ * @return array<int, array<string, mixed>> An array of filter arrays with keys "id", "match_any_rule", "inverse", "rules", and "actions"
+ */
static function load_filters(int $feed_id, int $owner_uid) {
$filters = array();
@@ -1788,17 +1867,33 @@ class RSSUtils {
}
/**
- * Try to determine the favicon URL for a feed.
+ * Returns first determined favicon URL for a feed.
+ * @param string $url A feed or page URL
+ * @access public
+ * @return false|string The favicon URL string, or false if none was found.
+ */
+ static function get_favicon_url(string $url) {
+
+ $favicon_urls = self::get_favicon_urls($url);
+
+ if (count($favicon_urls) > 0)
+ return $favicon_urls[0];
+ else
+ return false;
+ }
+
+ /**
+ * Try to determine all favicon URLs for a feed.
* adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
* http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
*
* @param string $url A feed or page URL
* @access public
- * @return mixed The favicon URL, or false if none was found.
+ * @return array<string> List of all determined favicon URLs or an empty array
*/
- static function get_favicon_url(string $url) {
+ static function get_favicon_urls(string $url) : array {
- $favicon_url = false;
+ $favicon_urls = [];
if ($html = @UrlHelper::fetch($url)) {
@@ -1808,28 +1903,39 @@ class RSSUtils {
$base = $xpath->query('/html/head/base[@href]');
foreach ($base as $b) {
- $url = rewrite_relative_url($url, $b->getAttribute("href"));
+ $url = UrlHelper::rewrite_relative($url, $b->getAttribute("href"));
break;
}
- $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
+ $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon" or @rel="alternate icon"]');
if (count($entries) > 0) {
foreach ($entries as $entry) {
- $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
- break;
+ $favicon_url = UrlHelper::rewrite_relative($url, $entry->getAttribute("href"));
+
+ if ($favicon_url)
+ array_push($favicon_urls, $favicon_url);
+
}
}
}
}
- if (!$favicon_url)
- $favicon_url = rewrite_relative_url($url, "/favicon.ico");
+ if (count($favicon_urls) == 0) {
+ $favicon_url = UrlHelper::rewrite_relative($url, "/favicon.ico");
+
+ if ($favicon_url)
+ array_push($favicon_urls, $favicon_url);
+ }
- return $favicon_url;
+ return $favicon_urls;
}
- // https://community.tt-rss.org/t/problem-with-img-srcset/3519
- static function decode_srcset($srcset) {
+ /**
+ * @see https://community.tt-rss.org/t/problem-with-img-srcset/3519
+ *
+ * @return array<int, array<string, string>> An array of srcset subitem arrays with keys "url" and "size"
+ */
+ static function decode_srcset(string $srcset): array {
$matches = [];
preg_match_all(
@@ -1847,7 +1953,10 @@ class RSSUtils {
return $matches;
}
- static function encode_srcset($matches) {
+ /**
+ * @param array<int, array<string, string>> $matches An array of srcset subitem arrays with keys "url" and "size"
+ */
+ static function encode_srcset(array $matches): string {
$tokens = [];
foreach ($matches as $m) {
@@ -1857,7 +1966,7 @@ class RSSUtils {
return implode(",", $tokens);
}
- static function function_enabled($func) {
+ static function function_enabled(string $func): bool {
return !in_array($func,
explode(',', str_replace(" ", "", ini_get('disable_functions'))));
}