diff options
Diffstat (limited to 'classes/rssutils.php')
-rwxr-xr-x | classes/rssutils.php | 196 |
1 files changed, 134 insertions, 62 deletions
diff --git a/classes/rssutils.php b/classes/rssutils.php index 216792a0e..9995b0e43 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,6 +1667,9 @@ 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"; @@ -1672,11 +1734,14 @@ class RSSUtils { return $icon_file; } - 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(); @@ -1794,7 +1859,7 @@ class RSSUtils { * * @param string $url A feed or page URL * @access public - * @return mixed The favicon URL, or false if none was found. + * @return false|string The favicon URL string, or false if none was found. */ static function get_favicon_url(string $url) { @@ -1808,14 +1873,14 @@ 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"]'); if (count($entries) > 0) { foreach ($entries as $entry) { - $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href")); + $favicon_url = UrlHelper::rewrite_relative($url, $entry->getAttribute("href")); break; } } @@ -1823,13 +1888,17 @@ class RSSUtils { } if (!$favicon_url) - $favicon_url = rewrite_relative_url($url, "/favicon.ico"); + $favicon_url = UrlHelper::rewrite_relative($url, "/favicon.ico"); return $favicon_url; } - // 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 +1916,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 +1929,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')))); } |