diff --git a/classes/API.php b/classes/API.php index 6dbb1f246..826d1f333 100644 --- a/classes/API.php +++ b/classes/API.php @@ -235,7 +235,7 @@ class API extends Handler { } function updateArticle(): bool { - $article_ids = explode(",", clean($_REQUEST["article_ids"])); + $article_ids = array_filter(explode(",", clean($_REQUEST["article_ids"] ?? ""))); $mode = (int) clean($_REQUEST["mode"]); $data = clean($_REQUEST["data"] ?? ""); $field_raw = (int)clean($_REQUEST["field"]); @@ -300,10 +300,9 @@ class API extends Handler { } function getArticle(): bool { - $article_ids = explode(',', clean($_REQUEST['article_id'] ?? '')); + $article_ids = array_filter(explode(',', clean($_REQUEST['article_id'] ?? ''))); $sanitize_content = self::_param_to_bool($_REQUEST['sanitize'] ?? true); - // @phpstan-ignore-next-line if (count($article_ids)) { $entries = ORM::for_table('ttrss_entries') ->table_alias('e') @@ -550,26 +549,22 @@ class API extends Handler { /* Virtual feeds */ - $vfeeds = PluginHost::getInstance()->get_feeds(Feeds::CATEGORY_SPECIAL); + foreach (PluginHost::getInstance()->get_feeds(Feeds::CATEGORY_SPECIAL) as $feed) { + if (!implements_interface($feed['sender'], 'IVirtualFeed')) + continue; - if (is_array($vfeeds)) { - foreach ($vfeeds as $feed) { - if (!implements_interface($feed['sender'], 'IVirtualFeed')) - continue; + /** @var IVirtualFeed $feed['sender'] */ + $unread = $feed['sender']->get_unread($feed['id']); - /** @var IVirtualFeed $feed['sender'] */ - $unread = $feed['sender']->get_unread($feed['id']); + if ($unread || !$unread_only) { + $row = [ + 'id' => PluginHost::pfeed_to_feed_id($feed['id']), + 'title' => $feed['title'], + 'unread' => $unread, + 'cat_id' => Feeds::CATEGORY_SPECIAL, + ]; - if ($unread || !$unread_only) { - $row = [ - 'id' => PluginHost::pfeed_to_feed_id($feed['id']), - 'title' => $feed['title'], - 'unread' => $unread, - 'cat_id' => Feeds::CATEGORY_SPECIAL, - ]; - - array_push($feeds, $row); - } + array_push($feeds, $row); } } @@ -696,7 +691,6 @@ class API extends Handler { if (!$is_cat && is_numeric($feed_id) && $feed_id < PLUGIN_FEED_BASE_INDEX && $feed_id > LABEL_BASE_INDEX) { $pfeed_id = PluginHost::feed_to_pfeed_id($feed_id); - /** @var IVirtualFeed|false $handler */ $handler = PluginHost::getInstance()->get_feed_handler($pfeed_id); if ($handler) { @@ -858,7 +852,7 @@ class API extends Handler { array_push($headlines, $headline_row); } - } else if (is_numeric($result) && $result == -1) { + } else if ($result == -1) { $headlines_header['first_id_changed'] = true; } diff --git a/classes/Counters.php b/classes/Counters.php index 0f6b419ba..99d4fdab1 100644 --- a/classes/Counters.php +++ b/classes/Counters.php @@ -264,25 +264,21 @@ class Counters { array_push($ret, $cv); } - $feeds = PluginHost::getInstance()->get_feeds(Feeds::CATEGORY_SPECIAL); + foreach (PluginHost::getInstance()->get_feeds(Feeds::CATEGORY_SPECIAL) as $feed) { + if (!implements_interface($feed['sender'], 'IVirtualFeed')) + continue; - if (is_array($feeds)) { - foreach ($feeds as $feed) { - /** @var IVirtualFeed $feed['sender'] */ + /** @var Plugin&IVirtualFeed $feed['sender'] */ - if (!implements_interface($feed['sender'], 'IVirtualFeed')) - continue; + $cv = [ + "id" => PluginHost::pfeed_to_feed_id($feed['id']), + "counter" => $feed['sender']->get_unread($feed['id']) + ]; - $cv = [ - "id" => PluginHost::pfeed_to_feed_id($feed['id']), - "counter" => $feed['sender']->get_unread($feed['id']) - ]; + if (method_exists($feed['sender'], 'get_total')) + $cv["auxcounter"] = $feed['sender']->get_total($feed['id']); - if (method_exists($feed['sender'], 'get_total')) - $cv["auxcounter"] = $feed['sender']->get_total($feed['id']); - - array_push($ret, $cv); - } + array_push($ret, $cv); } return $ret; diff --git a/classes/Debug.php b/classes/Debug.php index 1cda12539..ba39b4d44 100644 --- a/classes/Debug.php +++ b/classes/Debug.php @@ -77,7 +77,7 @@ class Debug { */ public static function map_loglevel(int $level) : int { if (in_array($level, self::ALL_LOG_LEVELS)) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore return.type (yes it is a Debug::LOG_* value) */ return $level; } else { user_error("Passed invalid debug log level: $level", E_USER_WARNING); diff --git a/classes/Digest.php b/classes/Digest.php index cad808711..287f5f8fc 100644 --- a/classes/Digest.php +++ b/classes/Digest.php @@ -163,7 +163,7 @@ class Digest $article_labels = Article::_get_labels($line["ref_id"], $user_id); $article_labels_formatted = ""; - if (is_array($article_labels) && count($article_labels) > 0) { + if (count($article_labels) > 0) { $article_labels_formatted = implode(", ", array_map(fn($a) => $a[1], $article_labels)); } diff --git a/classes/FeedItem_Atom.php b/classes/FeedItem_Atom.php index f6c96f959..b5eaca181 100644 --- a/classes/FeedItem_Atom.php +++ b/classes/FeedItem_Atom.php @@ -43,7 +43,6 @@ class FeedItem_Atom extends FeedItem_Common { $links = $this->elem->getElementsByTagName("link"); foreach ($links as $link) { - /** @phpstan-ignore-next-line */ if ($link->hasAttribute("href") && (!$link->hasAttribute("rel") || $link->getAttribute("rel") == "alternate" @@ -181,7 +180,6 @@ class FeedItem_Atom extends FeedItem_Common { $encs = []; foreach ($links as $link) { - /** @phpstan-ignore-next-line */ if ($link->hasAttribute("href") && $link->hasAttribute("rel")) { $base = $this->xpath->evaluate("string(ancestor-or-self::*[@xml:base][1]/@xml:base)", $link); diff --git a/classes/FeedItem_RSS.php b/classes/FeedItem_RSS.php index b5710ef4f..207d54dfb 100644 --- a/classes/FeedItem_RSS.php +++ b/classes/FeedItem_RSS.php @@ -34,7 +34,7 @@ class FeedItem_RSS extends FeedItem_Common { $links = $this->xpath->query("atom:link", $this->elem); foreach ($links as $link) { - if ($link && $link->hasAttribute("href") && + if ($link->hasAttribute("href") && (!$link->hasAttribute("rel") || $link->getAttribute("rel") == "alternate" || $link->getAttribute("rel") == "standout")) { diff --git a/classes/Feeds.php b/classes/Feeds.php index 34540ff11..b4df690a7 100644 --- a/classes/Feeds.php +++ b/classes/Feeds.php @@ -103,8 +103,6 @@ class Feeds extends Handler_Protected { $qfh_ret = []; if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { - - /** @var IVirtualFeed|false $handler */ $handler = PluginHost::getInstance()->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -246,7 +244,7 @@ class Feeds extends Handler_Protected { $label_cache = json_decode($label_cache, true); if ($label_cache) { - if ($label_cache["no-labels"] ?? 0 == 1) + if (($label_cache["no-labels"] ?? 0) == 1) $labels = []; else $labels = $label_cache; @@ -936,15 +934,10 @@ class Feeds extends Handler_Protected { if ($is_cat) { return self::_get_cat_unread($n_feed, $owner_uid); - } else if(is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { // virtual Feed + } else if (is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { // virtual Feed $feed_id = PluginHost::feed_to_pfeed_id($feed); $handler = PluginHost::getInstance()->get_feed_handler($feed_id); - if (implements_interface($handler, 'IVirtualFeed')) { - /** @var IVirtualFeed $handler */ - return $handler->get_unread($feed_id); - } else { - return 0; - } + return $handler ? $handler->get_unread($feed_id) : 0; } else if ($n_feed == Feeds::FEED_RECENTLY_READ) { return 0; // tags @@ -1067,7 +1060,7 @@ class Feeds extends Handler_Protected { if (!$url) return ["code" => 2]; PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_PRE_SUBSCRIBE, - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore closure.unusedUse, closure.unusedUse, closure.unusedUse */ function ($result) use (&$url, &$auth_login, &$auth_pass) { // arguments are updated inside the hook (if needed) }, diff --git a/classes/Handler_Public.php b/classes/Handler_Public.php index 208c8e40a..518dcad05 100644 --- a/classes/Handler_Public.php +++ b/classes/Handler_Public.php @@ -54,8 +54,6 @@ class Handler_Public extends Handler { PluginHost::feed_to_pfeed_id((int)$feed)); if ($handler) { - // 'get_headlines' is implemented by the plugin. - // @phpstan-ignore-next-line $qfh_ret = $handler->get_headlines(PluginHost::feed_to_pfeed_id((int)$feed), $params); } else { user_error("Failed to find handler for plugin feed ID: $feed", E_USER_ERROR); diff --git a/classes/Plugin.php b/classes/Plugin.php index cc50ae75e..62211f338 100644 --- a/classes/Plugin.php +++ b/classes/Plugin.php @@ -76,7 +76,7 @@ abstract class Plugin { * * @return string */ function __($msgid) { - /** @var Plugin $this -- this is a strictly template-related hack */ + // this is a strictly template-related hack return _dgettext(PluginHost::object_to_domain($this), $msgid); } @@ -87,7 +87,7 @@ abstract class Plugin { * * @return string */ function _ngettext($singular, $plural, $number) { - /** @var Plugin $this -- this is a strictly template-related hack */ + // this is a strictly template-related hack return _dngettext(PluginHost::object_to_domain($this), $singular, $plural, $number); } diff --git a/classes/PluginHost.php b/classes/PluginHost.php index 23ff03661..608355158 100644 --- a/classes/PluginHost.php +++ b/classes/PluginHost.php @@ -386,7 +386,7 @@ class PluginHost { * @param PluginHost::HOOK_* $type */ function del_hook(string $type, Plugin $sender): void { - if (is_array($this->hooks[$type])) { + if (array_key_exists($type, $this->hooks)) { foreach (array_keys($this->hooks[$type]) as $prio) { $key = array_search($sender, $this->hooks[$type][$prio]); @@ -589,7 +589,7 @@ class PluginHost { } } - /** @return array> command type -> details array */ + /** @return array command type -> details array */ function get_commands() { return $this->commands; } @@ -782,12 +782,21 @@ class PluginHost { return $this->feeds[$cat_id] ?? []; } - // convert feed_id (e.g. -129) to pfeed_id first + /** + * convert feed_id (e.g. -129) to pfeed_id first + * + * @return (Plugin&IVirtualFeed)|null + */ function get_feed_handler(int $pfeed_id): ?Plugin { foreach ($this->feeds as $cat) { foreach ($cat as $feed) { if ($feed['id'] == $pfeed_id) { - return $feed['sender']; + if (implements_interface($feed['sender'], 'IVirtualFeed')) { + /** @var Plugin&IVirtualFeed $feed['sender'] */ + return $feed['sender']; + } else { + return null; + } } } } diff --git a/classes/Pref_Feeds.php b/classes/Pref_Feeds.php index 5ca557c84..8101b9531 100644 --- a/classes/Pref_Feeds.php +++ b/classes/Pref_Feeds.php @@ -240,8 +240,7 @@ class Pref_Feeds extends Handler_Protected { /** * Uncategorized is a special case. * - * Define a minimal array shape to help PHPStan with the type of $cat['items'] - * @var array{items: array>} $cat + * @var array{id: string, bare_id: int, auxcounter: int, name: string, items: array>, type: 'category', checkbox: bool, unread: int, child_unread: int} */ $cat = [ 'id' => 'CAT:0', diff --git a/classes/Pref_Filters.php b/classes/Pref_Filters.php index 7a477d7db..87237342a 100644 --- a/classes/Pref_Filters.php +++ b/classes/Pref_Filters.php @@ -115,9 +115,6 @@ class Pref_Filters extends Handler_Protected { $glue = $filter['match_any_rule'] ? " OR " : " AND "; $scope_qpart = join($glue, $scope_qparts); - /** @phpstan-ignore-next-line */ - if (!$scope_qpart) $scope_qpart = "true"; - $rv = array(); //while ($found < $limit && $offset < $limit * 1000 && time() - $started < ini_get("max_execution_time") * 0.7) { @@ -714,7 +711,6 @@ class Pref_Filters extends Handler_Protected { } function editrule(): void { - /** @var array */ $feed_ids = explode(",", clean($_REQUEST["ids"])); print json_encode([ diff --git a/classes/Pref_Prefs.php b/classes/Pref_Prefs.php index dd28bd3f2..47d6886f9 100644 --- a/classes/Pref_Prefs.php +++ b/classes/Pref_Prefs.php @@ -177,7 +177,7 @@ class Pref_Prefs extends Handler_Protected { $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); if (implements_interface($authenticator, "IAuthModule2")) { - /** @var IAuthModule2 $authenticator */ + /** @var Plugin&IAuthModule2 $authenticator */ print format_notice($authenticator->change_password($_SESSION["uid"], $old_pw, $new_pw)); } else { print "ERROR: ".format_error("Function not supported by authentication module."); @@ -976,7 +976,7 @@ class Pref_Prefs extends Handler_Protected { $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); - /** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */ + /** @var Auth_Internal|null $authenticator -- this is only here to make check_password() visible to static analyzer */ if ($authenticator->check_password($_SESSION["uid"], $password)) { if (UserHelper::enable_otp($_SESSION["uid"], $otp_check)) { print "OK"; @@ -991,7 +991,7 @@ class Pref_Prefs extends Handler_Protected { function otpdisable(): void { $password = clean($_REQUEST["password"]); - /** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */ + /** @var Auth_Internal|null $authenticator -- this is only here to make check_password() visible to static analyzer */ $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); if ($authenticator->check_password($_SESSION["uid"], $password)) { @@ -1316,14 +1316,8 @@ class Pref_Prefs extends Handler_Protected { function updateLocalPlugins(): void { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { - $plugins = explode(",", $_REQUEST["plugins"] ?? ""); - - if ($plugins !== false) { - $plugins = array_filter($plugins, 'strlen'); - } - + $plugins = array_filter(explode(",", $_REQUEST["plugins"] ?? ""), "strlen"); $root_dir = Config::get_self_dir(); - $rv = []; if ($plugins) { diff --git a/classes/UserHelper.php b/classes/UserHelper.php index 0c2ed349b..27e022624 100644 --- a/classes/UserHelper.php +++ b/classes/UserHelper.php @@ -50,7 +50,7 @@ class UserHelper { */ public static function map_access_level(int $level) : int { if (in_array($level, self::ACCESS_LEVELS)) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore return.type (yes it is a UserHelper::ACCESS_LEVEL_* value) */ return $level; } else { user_error("Passed invalid user access level: $level", E_USER_WARNING); @@ -503,7 +503,6 @@ class UserHelper { return $authenticator->check_password($owner_uid, $password); } else { - /** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */ $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); if ($authenticator && diff --git a/composer.json b/composer.json index 25e22ad4c..e91c8d2e7 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "guzzlehttp/guzzle": "^7.0" }, "require-dev": { - "phpstan/phpstan": "1.10.3", + "phpstan/phpstan": "2.0.1", "phpunit/phpunit": "9.5.16", "phpunit/php-code-coverage": "^9.2" } diff --git a/composer.lock b/composer.lock index e23b042a1..96bd47dc0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "478a27f2e837c92757db206b443c67a4", + "content-hash": "f071cb2cab2cf7d4ef32c4eb7655e350", "packages": [ { "name": "beberlei/assert", @@ -1781,20 +1781,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" + "reference": "ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d", + "reference": "ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1819,8 +1819,11 @@ "static analysis" ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.3" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -1830,13 +1833,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2023-02-25T14:47:13+00:00" + "time": "2024-11-11T15:43:04+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/include/errorhandler.php b/include/errorhandler.php index ea464f77a..4f773bc19 100644 --- a/include/errorhandler.php +++ b/include/errorhandler.php @@ -2,40 +2,38 @@ /** * @param array> $trace */ -function format_backtrace($trace): string { +function format_backtrace(array $trace): string { $rv = ""; $idx = 1; - if (is_array($trace)) { - foreach ($trace as $e) { - if (isset($e["file"]) && isset($e["line"])) { - $fmt_args = []; + foreach ($trace as $e) { + if (isset($e["file"]) && isset($e["line"])) { + $fmt_args = []; - if (is_array($e["args"] ?? false)) { - foreach ($e["args"] as $a) { - if (is_object($a)) { - array_push($fmt_args, "{" . get_class($a) . "}"); - } else if (is_array($a)) { - array_push($fmt_args, "[" . truncate_string(json_encode($a), 256, "...")) . "]"; - } else if (is_resource($a)) { - array_push($fmt_args, truncate_string(get_resource_type($a), 256, "...")); - } else if (is_string($a)) { - array_push($fmt_args, truncate_string($a, 256, "...")); - } + if (is_array($e["args"] ?? false)) { + foreach ($e["args"] as $a) { + if (is_object($a)) { + array_push($fmt_args, "{" . get_class($a) . "}"); + } else if (is_array($a)) { + array_push($fmt_args, "[" . truncate_string(json_encode($a), 256, "...")) . "]"; + } else if (is_resource($a)) { + array_push($fmt_args, truncate_string(get_resource_type($a), 256, "...")); + } else if (is_string($a)) { + array_push($fmt_args, truncate_string($a, 256, "...")); } } - - $filename = str_replace(dirname(__DIR__) . "/", "", $e["file"]); - - $rv .= sprintf("%d. %s(%s): %s(%s)\n", - $idx, - $filename, - $e["line"], - $e["function"], - implode(", ", $fmt_args)); - - $idx++; } + + $filename = str_replace(dirname(__DIR__) . "/", "", $e["file"]); + + $rv .= sprintf("%d. %s(%s): %s(%s)\n", + $idx, + $filename, + $e["line"], + $e["function"], + implode(", ", $fmt_args)); + + $idx++; } } diff --git a/include/functions.php b/include/functions.php index c32924743..a79a19711 100644 --- a/include/functions.php +++ b/include/functions.php @@ -123,6 +123,7 @@ // create a list like "en" => 0.8 $langs = array_combine($lang_parse[1], $lang_parse[4]); + /** @phpstan-ignore function.alreadyNarrowedType (PHP 7.4 will return false if array_value has an issue) */ if (is_array($langs)) { // set default to 1 for any without q factor foreach ($langs as $lang => $val) { diff --git a/include/login_form.php b/include/login_form.php index 0545a51be..5ff2b378e 100755 --- a/include/login_form.php +++ b/include/login_form.php @@ -39,16 +39,14 @@ get_plugins() as $n => $p) { - if (method_exists($p, "get_login_js")) { - $script = $p->get_login_js(); + $script = $p->get_login_js(); - if ($script) { - echo "try { - $script - } catch (e) { - console.warn('failed to initialize plugin JS: $n', e); - }"; - } + if ($script) { + echo "try { + $script + } catch (e) { + console.warn('failed to initialize plugin JS: $n', e); + }"; } } ?> diff --git a/index.php b/index.php index a2a6d2373..ac37043cd 100644 --- a/index.php +++ b/index.php @@ -36,8 +36,9 @@