diff options
| author | 2024-06-09 20:32:12 +0200 | |
|---|---|---|
| committer | 2024-06-09 20:32:12 +0200 | |
| commit | 5b28a35003a015e29770094932157f13a3f7f5c0 (patch) | |
| tree | 4cbe4100379ca0d148115ad31f5a1c0c95ff7c80 /app/Models | |
| parent | e98c57841b843ed881f06ce6ed1c9c89942c27b8 (diff) | |
Pass PHPStan level 9 (#6544)
* More PHPStan
* More, passing
* 4 more files
* Update to PHPStan 1.11.4
Needed for fixed bug: Consider numeric-string types after string concat
https://github.com/phpstan/phpstan/releases/tag/1.11.4
* Pass PHPStan level 9
Start tracking booleansInConditions
* Fix mark as read
* Fix doctype
* ctype_digit
Diffstat (limited to 'app/Models')
| -rw-r--r-- | app/Models/DatabaseDAO.php | 6 | ||||
| -rw-r--r-- | app/Models/DatabaseDAOPGSQL.php | 2 | ||||
| -rw-r--r-- | app/Models/DatabaseDAOSQLite.php | 2 | ||||
| -rw-r--r-- | app/Models/Entry.php | 22 | ||||
| -rw-r--r-- | app/Models/EntryDAO.php | 14 | ||||
| -rw-r--r-- | app/Models/Feed.php | 30 | ||||
| -rw-r--r-- | app/Models/ReadingMode.php | 8 | ||||
| -rw-r--r-- | app/Models/UserConfiguration.php | 2 | ||||
| -rw-r--r-- | app/Models/UserQuery.php | 12 |
9 files changed, 58 insertions, 40 deletions
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php index d0938ca62..b57138093 100644 --- a/app/Models/DatabaseDAO.php +++ b/app/Models/DatabaseDAO.php @@ -81,7 +81,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { return count(array_keys($tables, true, true)) === count($tables); } - /** @return array<array<string,string|int|bool|null>> */ + /** @return array<array{name:string,type:string,notnull:bool,default:mixed}> */ public function getSchema(string $table): array { $res = $this->fetchAssoc('DESC `_' . $table . '`'); return $res == null ? [] : $this->listDaoToSchema($res); @@ -160,7 +160,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { /** * @param array<string,string|int|bool|null> $dao - * @return array{'name':string,'type':string,'notnull':bool,'default':mixed} + * @return array{name:string,type:string,notnull:bool,default:mixed} */ public function daoToSchema(array $dao): array { return [ @@ -173,7 +173,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo { /** * @param array<array<string,string|int|bool|null>> $listDAO - * @return array<array<string,string|int|bool|null>> + * @return array<array{name:string,type:string,notnull:bool,default:mixed}> */ public function listDaoToSchema(array $listDAO): array { $list = []; diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php index e6895e6f1..3cce4b062 100644 --- a/app/Models/DatabaseDAOPGSQL.php +++ b/app/Models/DatabaseDAOPGSQL.php @@ -34,7 +34,7 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite { return count(array_keys($tables, true, true)) === count($tables); } - /** @return array<array<string,string|int|bool|null>> */ + /** @return array<array{name:string,type:string,notnull:bool,default:mixed}> */ #[\Override] public function getSchema(string $table): array { $sql = <<<'SQL' diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php index 0ac6ee8f5..3e15578e8 100644 --- a/app/Models/DatabaseDAOSQLite.php +++ b/app/Models/DatabaseDAOSQLite.php @@ -30,7 +30,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { return count(array_keys($tables, true, true)) == count($tables); } - /** @return array<array<string,string|int|bool|null>> */ + /** @return array<array{name:string,type:string,notnull:bool,default:mixed}> */ #[\Override] public function getSchema(string $table): array { $sql = 'PRAGMA table_info(' . $table . ')'; diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 2325252f7..e102c5c21 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -10,6 +10,7 @@ class FreshRSS_Entry extends Minz_Model { public const STATE_FAVORITE = 4; public const STATE_NOT_FAVORITE = 8; + /** @var numeric-string */ private string $id = '0'; private string $guid; private string $title; @@ -110,6 +111,7 @@ class FreshRSS_Entry extends Minz_Model { } } + /** @return numeric-string */ public function id(): string { return $this->id; } @@ -195,8 +197,8 @@ class FreshRSS_Entry extends Minz_Model { $thumbnailAttribute = $this->attributeArray('thumbnail') ?? []; if (!empty($thumbnailAttribute['url'])) { $elink = $thumbnailAttribute['url']; - if ($allowDuplicateEnclosures || !self::containsLink($content, $elink)) { - $content .= <<<HTML + if (is_string($elink) && ($allowDuplicateEnclosures || !self::containsLink($content, $elink))) { + $content .= <<<HTML <figure class="enclosure"> <p class="enclosure-content"> <img class="enclosure-thumbnail" src="{$elink}" alt="" /> @@ -216,7 +218,7 @@ HTML; continue; } $elink = $enclosure['url'] ?? ''; - if ($elink == '') { + if ($elink == '' || !is_string($elink)) { continue; } if (!$allowDuplicateEnclosures && self::containsLink($content, $elink)) { @@ -281,6 +283,7 @@ HTML; $attributeEnclosures = $this->attributeArray('enclosures'); if (is_iterable($attributeEnclosures)) { // FreshRSS 1.20.1+: The enclosures are saved as attributes + /** @var iterable<array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string|array<string>,'height'?:int,'width'?:int,'thumbnails'?:array<string>}> $attributeEnclosures */ yield from $attributeEnclosures; } try { @@ -296,8 +299,10 @@ HTML; // Legacy code for database entries < FreshRSS 1.20.1 $enclosures = $xpath->query('//div[@class="enclosure"]/p[@class="enclosure-content"]/*[@src]'); if (!empty($enclosures)) { - /** @var DOMElement $enclosure */ foreach ($enclosures as $enclosure) { + if (!($enclosure instanceof DOMElement)) { + continue; + } $result = [ 'url' => $enclosure->getAttribute('src'), 'type' => $enclosure->getAttribute('data-type'), @@ -318,8 +323,10 @@ HTML; if ($searchBodyImages && $xpath !== null) { $images = $xpath->query('//img'); if (!empty($images)) { - /** @var DOMElement $img */ foreach ($images as $img) { + if (!($img instanceof DOMElement)) { + continue; + } $src = $img->getAttribute('src'); if ($src == null) { $src = $img->getAttribute('data-src'); @@ -346,6 +353,7 @@ HTML; $thumbnail = $this->attributeArray('thumbnail') ?? []; // First, use the provided thumbnail, if any if (!empty($thumbnail['url'])) { + /** @var array{'url':string,'height'?:int,'width'?:int,'time'?:string} $thumbnail */ return $thumbnail; } if ($searchEnclosures) { @@ -467,7 +475,7 @@ HTML; return $this->hash; } - /** @param int|string $value String is for compatibility with 32-bit platforms */ + /** @param int|numeric-string $value String is for compatibility with 32-bit platforms */ public function _id($value): void { if (is_int($value)) { $value = (string)$value; @@ -882,7 +890,7 @@ HTML; /** * Integer format conversion for Google Reader API format - * @param string|int $dec Decimal number + * @param numeric-string|int $dec Decimal number * @return string 64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId */ private static function dec2hex($dec): string { diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index bb0b9af43..0b289eb41 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -321,7 +321,7 @@ SQL; * @todo simplify the query by removing the str_repeat. I am pretty sure * there is an other way to do that. * - * @param string|array<string> $ids + * @param numeric-string|array<numeric-string> $ids * @return int|false */ public function markFavorite($ids, bool $is_favorite = true) { @@ -399,7 +399,7 @@ SQL; * Toggle the read marker on one or more article. * Then the cache is updated. * - * @param string|array<string> $ids + * @param numeric-string|array<numeric-string> $ids * @param bool $is_read * @return int|false affected rows */ @@ -465,7 +465,7 @@ SQL; * * If $idMax equals 0, a deprecated debug message is logged * - * @param string $idMax fail safe article ID + * @param numeric-string $idMax fail safe article ID * @return int|false affected rows */ public function markReadEntries(string $idMax = '0', bool $onlyFavorites = false, ?int $priorityMin = null, ?int $prioritMax = null, @@ -517,7 +517,7 @@ SQL; * If $idMax equals 0, a deprecated debug message is logged * * @param int $id category ID - * @param string $idMax fail safe article ID + * @param numeric-string $idMax fail safe article ID * @return int|false affected rows */ public function markReadCat(int $id, string $idMax = '0', ?FreshRSS_BooleanSearch $filters = null, int $state = 0, bool $is_read = true) { @@ -558,7 +558,7 @@ SQL; * If $idMax equals 0, a deprecated debug message is logged * * @param int $id_feed feed ID - * @param string $idMax fail safe article ID + * @param numeric-string $idMax fail safe article ID * @return int|false affected rows */ public function markReadFeed(int $id_feed, string $idMax = '0', ?FreshRSS_BooleanSearch $filters = null, int $state = 0, bool $is_read = true) { @@ -612,7 +612,7 @@ SQL; /** * Mark all the articles in a tag as read. * @param int $id tag ID, or empty for targeting any tag - * @param string $idMax max article ID + * @param numeric-string $idMax max article ID * @return int|false affected rows */ public function markReadTag(int $id = 0, string $idMax = '0', ?FreshRSS_BooleanSearch $filters = null, @@ -1206,7 +1206,7 @@ SQL; } /** - * @param array<string> $ids + * @param array<numeric-string> $ids * @param 'ASC'|'DESC' $order * @return Traversable<FreshRSS_Entry> */ diff --git a/app/Models/Feed.php b/app/Models/Feed.php index ebe69a84c..0274ded0a 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -658,7 +658,7 @@ class FreshRSS_Feed extends Minz_Model { //check if the content is actual JSON $jf = json_decode($json, true); - if (json_last_error() !== JSON_ERROR_NONE) { + if (json_last_error() !== JSON_ERROR_NONE || !is_array($jf)) { return null; } @@ -734,9 +734,14 @@ class FreshRSS_Feed extends Minz_Model { } $xpath = new DOMXPath($doc); + $xpathEvaluateString = function (string $expression, ?DOMNode $contextNode = null) use ($xpath): string { + $result = @$xpath->evaluate('normalize-space(' . $expression . ')', $contextNode); + return is_string($result) ? $result : ''; + }; + $view->rss_title = $xPathFeedTitle == '' ? $this->name() : - htmlspecialchars(@$xpath->evaluate('normalize-space(' . $xPathFeedTitle . ')'), ENT_COMPAT, 'UTF-8'); - $view->rss_base = htmlspecialchars(trim($xpath->evaluate('normalize-space(//base/@href)')), ENT_COMPAT, 'UTF-8'); + htmlspecialchars($xpathEvaluateString($xPathFeedTitle), ENT_COMPAT, 'UTF-8'); + $view->rss_base = htmlspecialchars(trim($xpathEvaluateString('//base/@href')), ENT_COMPAT, 'UTF-8'); $nodes = $xpath->query($xPathItem); if ($nodes === false || $nodes->length === 0) { return null; @@ -744,7 +749,7 @@ class FreshRSS_Feed extends Minz_Model { foreach ($nodes as $node) { $item = []; - $item['title'] = $xPathItemTitle == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemTitle . ')', $node); + $item['title'] = $xPathItemTitle == '' ? '' : $xpathEvaluateString($xPathItemTitle, $node); $item['content'] = ''; if ($xPathItemContent != '') { @@ -756,36 +761,35 @@ class FreshRSS_Feed extends Minz_Model { $content .= $doc->saveHTML($child) . "\n"; } $item['content'] = $content; - } else { + } elseif (is_string($result) || is_int($result) || is_bool($result)) { // Typed expression, save as-is $item['content'] = (string)$result; } } - $item['link'] = $xPathItemUri == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemUri . ')', $node); - $item['author'] = $xPathItemAuthor == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemAuthor . ')', $node); - $item['timestamp'] = $xPathItemTimestamp == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemTimestamp . ')', $node); + $item['link'] = $xPathItemUri == '' ? '' : $xpathEvaluateString($xPathItemUri, $node); + $item['author'] = $xPathItemAuthor == '' ? '' : $xpathEvaluateString($xPathItemAuthor, $node); + $item['timestamp'] = $xPathItemTimestamp == '' ? '' : $xpathEvaluateString($xPathItemTimestamp, $node); if ($xPathItemTimeFormat != '') { - $dateTime = DateTime::createFromFormat($xPathItemTimeFormat, $item['timestamp'] ?? ''); + $dateTime = DateTime::createFromFormat($xPathItemTimeFormat, $item['timestamp']); if ($dateTime != false) { $item['timestamp'] = $dateTime->format(DateTime::ATOM); } } - $item['thumbnail'] = $xPathItemThumbnail == '' ? '' : @$xpath->evaluate('normalize-space(' . $xPathItemThumbnail . ')', $node); + $item['thumbnail'] = $xPathItemThumbnail == '' ? '' : $xpathEvaluateString($xPathItemThumbnail, $node); if ($xPathItemCategories != '') { $itemCategories = @$xpath->evaluate($xPathItemCategories, $node); if (is_string($itemCategories) && $itemCategories !== '') { $item['tags'] = [$itemCategories]; } elseif ($itemCategories instanceof DOMNodeList && $itemCategories->length > 0) { $item['tags'] = []; - /** @var DOMNode $itemCategory */ foreach ($itemCategories as $itemCategory) { $item['tags'][] = $itemCategory->textContent; } } } if ($xPathItemUid != '') { - $item['guid'] = @$xpath->evaluate('normalize-space(' . $xPathItemUid . ')', $node); + $item['guid'] = $xpathEvaluateString($xPathItemUid, $node); } if (empty($item['guid'])) { $item['guid'] = 'urn:sha1:' . sha1($item['title'] . $item['content'] . $item['link']); @@ -958,7 +962,7 @@ class FreshRSS_Feed extends Minz_Model { $hubFilename = PSHB_PATH . '/feeds/' . sha1($url) . '/!hub.json'; $hubFile = @file_get_contents($hubFilename); $hubJson = is_string($hubFile) ? json_decode($hubFile, true) : null; - if (is_array($hubJson) && !isset($hubJson['error']) || $hubJson['error'] !== $error) { + if (is_array($hubJson) && (!isset($hubJson['error']) || $hubJson['error'] !== $error)) { $hubJson['error'] = $error; file_put_contents($hubFilename, json_encode($hubJson)); Minz_Log::warning('Set error to ' . ($error ? 1 : 0) . ' for ' . $url, PSHB_LOG); diff --git a/app/Models/ReadingMode.php b/app/Models/ReadingMode.php index c8ebe66f3..035b22114 100644 --- a/app/Models/ReadingMode.php +++ b/app/Models/ReadingMode.php @@ -9,13 +9,13 @@ class FreshRSS_ReadingMode { protected string $id; protected string $name; protected string $title; - /** @var array{'c':string,'a':string,'params':array<string,mixed>} */ + /** @var array{c:string,a:string,params:array<string,mixed>} */ protected array $urlParams; protected bool $isActive = false; /** * ReadingMode constructor. - * @param array{'c':string,'a':string,'params':array<string,mixed>} $urlParams + * @param array{c:string,a:string,params:array<string,mixed>} $urlParams */ public function __construct(string $id, string $title, array $urlParams, bool $active) { $this->id = $id; @@ -47,12 +47,12 @@ class FreshRSS_ReadingMode { return $this; } - /** @return array{'c':string,'a':string,'params':array<string,mixed>} */ + /** @return array{c:string,a:string,params:array<string,mixed>} */ public function getUrlParams(): array { return $this->urlParams; } - /** @param array{'c':string,'a':string,'params':array<string,mixed>} $urlParams */ + /** @param array{c:string,a:string,params:array<string,mixed>} $urlParams */ public function setUrlParams(array $urlParams): FreshRSS_ReadingMode { $this->urlParams = $urlParams; return $this; diff --git a/app/Models/UserConfiguration.php b/app/Models/UserConfiguration.php index 0b02960c4..c9d5b07af 100644 --- a/app/Models/UserConfiguration.php +++ b/app/Models/UserConfiguration.php @@ -69,7 +69,7 @@ declare(strict_types=1); * @property int $dynamic_opml_ttl_default * @property-read bool $unsafe_autologin_enabled * @property string $view_mode - * @property array<string,mixed> $volatile + * @property array<string,bool|int|string> $volatile * @property array<string,array<string,mixed>> $extensions */ final class FreshRSS_UserConfiguration extends Minz_Configuration { diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php index 73bf52ef8..3058483d3 100644 --- a/app/Models/UserQuery.php +++ b/app/Models/UserQuery.php @@ -41,7 +41,8 @@ class FreshRSS_UserQuery { } /** - * @param array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string,shareRss?:bool,shareOpml?:bool} $query + * @param array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string, + * shareRss?:bool,shareOpml?:bool,description?:string,imageUrl?:string} $query * @param array<int,FreshRSS_Category> $categories * @param array<int,FreshRSS_Tag> $labels */ @@ -61,8 +62,13 @@ class FreshRSS_UserQuery { } if (empty($query['url'])) { if (!empty($query)) { - unset($query['name']); - $this->url = Minz_Url::display(['params' => $query]); + $link = $query; + unset($link['description']); + unset($link['imageUrl']); + unset($link['name']); + unset($link['shareOpml']); + unset($link['shareRss']); + $this->url = Minz_Url::display(['params' => $link]); } } else { $this->url = $query['url']; |
