aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2024-12-27 12:12:49 +0100
committerGravatar GitHub <noreply@github.com> 2024-12-27 12:12:49 +0100
commitb1d24fbdb7d1cc948c946295035dad6df550fb7e (patch)
tree7b4365a04097a779659474fbb9281a9661512522 /app/Models
parent897e4a3f4a273d50c28157edb67612b2d7fa2e6f (diff)
PHPStan 2.0 (#7131)
* PHPStan 2.0 fix https://github.com/FreshRSS/FreshRSS/issues/6989 https://github.com/phpstan/phpstan/releases/tag/2.0.0 https://github.com/phpstan/phpstan/blob/2.0.x/UPGRADING.md * More * More * Done * fix i18n CLI * Restore a PHPStan Next test For work towards PHPStan Level 10 * 4 more on Level 10 * fix getTagsForEntry * API at Level 10 * More Level 10 * Finish Minz at Level 10 * Finish CLI at Level 10 * Finish Controllers at Level 10 * More Level 10 * More * Pass bleedingEdge * Clean PHPStan options and add TODOs * Level 10 for main config * More * Consitency array vs. list * Sanitize themes get_infos * Simplify TagDAO->getTagsForEntries() * Finish reportAnyTypeWideningInVarTag * Prepare checkBenevolentUnionTypes and checkImplicitMixed * Fixes * Refix * Another fix * Casing of __METHOD__ constant
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/ActionController.php1
-rw-r--r--app/Models/AttributesTrait.php1
-rw-r--r--app/Models/Auth.php4
-rw-r--r--app/Models/BooleanSearch.php4
-rw-r--r--app/Models/Category.php14
-rw-r--r--app/Models/CategoryDAO.php98
-rw-r--r--app/Models/CategoryDAOSQLite.php2
-rw-r--r--app/Models/Context.php49
-rw-r--r--app/Models/DatabaseDAO.php57
-rw-r--r--app/Models/DatabaseDAOPGSQL.php10
-rw-r--r--app/Models/DatabaseDAOSQLite.php21
-rw-r--r--app/Models/Entry.php20
-rw-r--r--app/Models/EntryDAO.php58
-rw-r--r--app/Models/EntryDAOPGSQL.php2
-rw-r--r--app/Models/EntryDAOSQLite.php2
-rw-r--r--app/Models/Feed.php15
-rw-r--r--app/Models/FeedDAO.php87
-rw-r--r--app/Models/FeedDAOSQLite.php2
-rw-r--r--app/Models/FilterAction.php9
-rw-r--r--app/Models/FilterActionsTrait.php9
-rw-r--r--app/Models/FormAuth.php2
-rw-r--r--app/Models/LogDAO.php2
-rw-r--r--app/Models/ReadingMode.php2
-rw-r--r--app/Models/Search.php140
-rw-r--r--app/Models/Share.php20
-rw-r--r--app/Models/StatsDAO.php42
-rw-r--r--app/Models/TagDAO.php60
-rw-r--r--app/Models/Themes.php33
-rw-r--r--app/Models/UserConfiguration.php9
-rw-r--r--app/Models/UserDAO.php9
-rw-r--r--app/Models/View.php18
-rw-r--r--app/Models/ViewJavascript.php6
-rw-r--r--app/Models/ViewStats.php14
33 files changed, 402 insertions, 420 deletions
diff --git a/app/Models/ActionController.php b/app/Models/ActionController.php
index 27fdfa44d..072f1a3d6 100644
--- a/app/Models/ActionController.php
+++ b/app/Models/ActionController.php
@@ -5,6 +5,7 @@ abstract class FreshRSS_ActionController extends Minz_ActionController {
/**
* @var FreshRSS_View
+ * @phpstan-ignore property.phpDocType
*/
protected $view;
diff --git a/app/Models/AttributesTrait.php b/app/Models/AttributesTrait.php
index 8795d81d9..f30b11b5d 100644
--- a/app/Models/AttributesTrait.php
+++ b/app/Models/AttributesTrait.php
@@ -53,6 +53,7 @@ trait FreshRSS_AttributesTrait {
$values = json_decode($values, true);
}
if (is_array($values)) {
+ $values = array_filter($values, 'is_string', ARRAY_FILTER_USE_KEY);
$this->attributes = $values;
}
}
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index f65a59e03..5c861f1db 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -75,8 +75,8 @@ class FreshRSS_Auth {
if (!$login_ok && FreshRSS_Context::systemConf()->http_auth_auto_register) {
$email = null;
if (FreshRSS_Context::systemConf()->http_auth_auto_register_email_field !== '' &&
- isset($_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field])) {
- $email = (string)$_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field];
+ is_string($_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field] ?? null)) {
+ $email = $_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field];
}
$language = Minz_Translate::getLanguage(null, Minz_Request::getPreferredLanguages(), FreshRSS_Context::systemConf()->language);
Minz_Translate::init($language);
diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php
index 529bcd338..f7273151e 100644
--- a/app/Models/BooleanSearch.php
+++ b/app/Models/BooleanSearch.php
@@ -7,7 +7,7 @@ declare(strict_types=1);
class FreshRSS_BooleanSearch implements \Stringable {
private string $raw_input = '';
- /** @var array<FreshRSS_BooleanSearch|FreshRSS_Search> */
+ /** @var list<FreshRSS_BooleanSearch|FreshRSS_Search> */
private array $searches = [];
/**
@@ -400,7 +400,7 @@ class FreshRSS_BooleanSearch implements \Stringable {
/**
* Either a list of FreshRSS_BooleanSearch combined by implicit AND
* or a series of FreshRSS_Search combined by explicit OR
- * @return array<FreshRSS_BooleanSearch|FreshRSS_Search>
+ * @return list<FreshRSS_BooleanSearch|FreshRSS_Search>
*/
public function searches(): array {
return $this->searches;
diff --git a/app/Models/Category.php b/app/Models/Category.php
index cd8145e0c..5f87335f3 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -19,7 +19,7 @@ class FreshRSS_Category extends Minz_Model {
private string $name;
private int $nbFeeds = -1;
private int $nbNotRead = -1;
- /** @var array<FreshRSS_Feed>|null */
+ /** @var list<FreshRSS_Feed>|null */
private ?array $feeds = null;
/** @var bool|int */
private $hasFeedsWithError = false;
@@ -100,7 +100,7 @@ class FreshRSS_Category extends Minz_Model {
}
/**
- * @return array<int,FreshRSS_Feed>
+ * @return list<FreshRSS_Feed>
* @throws Minz_ConfigurationNamespaceException
* @throws Minz_PDOConnectionException
*/
@@ -142,11 +142,11 @@ class FreshRSS_Category extends Minz_Model {
}
/** @param array<FreshRSS_Feed>|FreshRSS_Feed $values */
- public function _feeds($values): void {
+ public function _feeds(array|FreshRSS_Feed $values): void {
if (!is_array($values)) {
$values = [$values];
}
- $this->feeds = $values;
+ $this->feeds = array_values($values);
$this->sortFeeds();
}
@@ -243,7 +243,7 @@ class FreshRSS_Category extends Minz_Model {
if ($this->feeds === null) {
return;
}
- uasort($this->feeds, static fn(FreshRSS_Feed $a, FreshRSS_Feed $b) => strnatcasecmp($a->name(), $b->name()));
+ usort($this->feeds, static fn(FreshRSS_Feed $a, FreshRSS_Feed $b) => strnatcasecmp($a->name(), $b->name()));
}
/**
@@ -265,13 +265,13 @@ class FreshRSS_Category extends Minz_Model {
/**
* Access cached feeds
* @param array<FreshRSS_Category> $categories
- * @return array<int,FreshRSS_Feed>
+ * @return list<FreshRSS_Feed>
*/
public static function findFeeds(array $categories): array {
$result = [];
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
- $result[$feed->id()] = $feed;
+ $result[] = $feed;
}
}
return $result;
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 6b563b0a8..556179800 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -19,7 +19,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if ($this->pdo->inTransaction()) {
$this->pdo->commit();
}
- Minz_Log::warning(__method__ . ': ' . $name);
+ Minz_Log::warning(__METHOD__ . ': ' . $name);
try {
if ($name === 'kind') { //v1.20.0
return $this->pdo->exec('ALTER TABLE `_category` ADD COLUMN kind SMALLINT DEFAULT 0') !== false;
@@ -30,8 +30,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
} elseif ('attributes' === $name) { //v1.15.0
$ok = $this->pdo->exec('ALTER TABLE `_category` ADD COLUMN attributes TEXT') !== false;
- /** @var array<array{'id':int,'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'keep_history':?int,'ttl':int,'attributes':string}> $feeds */
+ /** @var list<array{id:int,url:string,kind:int,category:int,name:string,website:string,lastUpdate:int,
+ * priority:int,pathEntries:string,httpAuth:string,error:int,keep_history:?int,ttl:int,attributes:string}> $feeds */
$feeds = $this->fetchAssoc('SELECT * FROM `_feed`') ?? [];
$stm = $this->pdo->prepare('UPDATE `_feed` SET attributes = :attributes WHERE id = :id');
@@ -51,15 +51,17 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if (!is_array($attributes)) {
$attributes = [];
}
+ $archiving = is_array($attributes['archiving'] ?? null) ? $attributes['archiving'] : [];
if ($keepHistory > 0) {
- $attributes['archiving']['keep_min'] = (int)$keepHistory;
+ $archiving['keep_min'] = (int)$keepHistory;
} elseif ($keepHistory == -1) { //Infinite
- $attributes['archiving']['keep_period'] = false;
- $attributes['archiving']['keep_max'] = false;
- $attributes['archiving']['keep_min'] = false;
+ $archiving['keep_period'] = false;
+ $archiving['keep_max'] = false;
+ $archiving['keep_min'] = false;
} else {
continue;
}
+ $attributes['archiving'] = $archiving;
if (!($stm->bindValue(':id', $feed['id'], PDO::PARAM_INT) &&
$stm->bindValue(':attributes', json_encode($attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) &&
$stm->execute())) {
@@ -78,12 +80,12 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
return $ok;
}
} catch (Exception $e) {
- Minz_Log::error(__method__ . ': ' . $e->getMessage());
+ Minz_Log::error(__METHOD__ . ': ' . $e->getMessage());
}
return false;
}
- /** @param array<string|int> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
protected function autoUpdateDb(array $errorInfo): bool {
if (isset($errorInfo[0])) {
if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
@@ -99,7 +101,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
}
/**
- * @param array{'name':string,'id'?:int,'kind'?:int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string|array<string,mixed>} $valuesTmp
+ * @param array{name:string,id?:int,kind?:int,lastUpdate?:int,error?:int|bool,attributes?:string|array<string,mixed>} $valuesTmp
*/
public function addCategory(array $valuesTmp): int|false {
// TRIM() to provide a type hint as text
@@ -127,6 +129,7 @@ SQL;
return $catId === false ? false : (int)$catId;
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->addCategory($valuesTmp);
}
@@ -150,7 +153,7 @@ SQL;
}
/**
- * @param array{'name':string,'kind':int,'attributes'?:array<string,mixed>|mixed|null} $valuesTmp
+ * @param array{name:string,kind:int,attributes?:array<string,mixed>|mixed|null} $valuesTmp
*/
public function updateCategory(int $id, array $valuesTmp): int|false {
// No tag of the same name
@@ -176,6 +179,7 @@ SQL;
return $stm->rowCount();
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->updateCategory($id, $valuesTmp);
}
@@ -217,21 +221,22 @@ SQL;
}
}
- /** @return Traversable<array{'id':int,'name':string,'kind':int,'lastUpdate':int,'error':int,'attributes'?:array<string,mixed>}> */
+ /** @return Traversable<array{id:int,name:string,kind:int,lastUpdate:int,error:int,attributes?:array<string,mixed>}> */
public function selectAll(): Traversable {
$sql = 'SELECT id, name, kind, `lastUpdate`, error, attributes FROM `_category`';
$stm = $this->pdo->query($sql);
if ($stm !== false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':int,'name':string,'kind':int,'lastUpdate':int,'error':int,'attributes'?:array<string,mixed>} $row */
+ /** @var array{id:int,name:string,kind:int,lastUpdate:int,error:int,attributes?:array<string,mixed>} $row */
yield $row;
}
} else {
$info = $this->pdo->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
yield from $this->selectAll();
} else {
- Minz_Log::error(__method__ . ' error: ' . json_encode($info));
+ Minz_Log::error(__METHOD__ . ' error: ' . json_encode($info));
}
}
}
@@ -239,24 +244,24 @@ SQL;
public function searchById(int $id): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE id=:id';
$res = $this->fetchAssoc($sql, ['id' => $id]) ?? [];
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
- $categories = self::daoToCategories($res);
+ /** @var array<array{name:string,id:int,kind:int,lastUpdate?:int,error:int|bool,attributes?:string}> $res */
+ $categories = self::daoToCategories($res); // @phpstan-ignore varTag.type
return reset($categories) ?: null;
}
public function searchByName(string $name): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE name=:name';
$res = $this->fetchAssoc($sql, ['name' => $name]) ?? [];
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
- $categories = self::daoToCategories($res);
+ /** @var array<array{name:string,id:int,kind:int,lastUpdate:int,error:int|bool,attributes:string}> $res */
+ $categories = self::daoToCategories($res); // @phpstan-ignore varTag.type
return reset($categories) ?: null;
}
- /** @return array<int,FreshRSS_Category> */
+ /** @return list<FreshRSS_Category> */
public function listSortedCategories(bool $prePopulateFeeds = true, bool $details = false): array {
$categories = $this->listCategories($prePopulateFeeds, $details);
- uasort($categories, static function (FreshRSS_Category $a, FreshRSS_Category $b) {
+ usort($categories, static function (FreshRSS_Category $a, FreshRSS_Category $b) {
$aPosition = $a->attributeInt('position');
$bPosition = $b->attributeInt('position');
if ($aPosition === $bPosition) {
@@ -272,7 +277,7 @@ SQL;
return $categories;
}
- /** @return array<int,FreshRSS_Category> */
+ /** @return list<FreshRSS_Category> */
public function listCategories(bool $prePopulateFeeds = true, bool $details = false): array {
if ($prePopulateFeeds) {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, c.kind AS c_kind, c.`lastUpdate` AS c_last_update, c.error AS c_error, c.attributes AS c_attributes, '
@@ -286,11 +291,12 @@ SQL;
$values = [ ':priority' => FreshRSS_Feed::PRIORITY_CATEGORY ];
if ($stm !== false && $stm->execute($values)) {
$res = $stm->fetchAll(PDO::FETCH_ASSOC) ?: [];
- /** @var array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string,
- * 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'category'?:int,'website'?:string,'priority'?:int,'error'?:int|bool,'attributes'?:string,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $res */
+ /** @var list<array{c_name:string,c_id:int,c_kind:int,c_last_update:int,c_error:int|bool,c_attributes?:string,
+ * id?:int,name?:string,url?:string,kind?:int,category?:int,website?:string,priority?:int,error?:int|bool,attributes?:string,cache_nbEntries?:int,cache_nbUnreads?:int,ttl?:int}> $res */
return self::daoToCategoriesPrepopulated($res);
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->listCategories($prePopulateFeeds, $details);
}
@@ -298,13 +304,13 @@ SQL;
return [];
}
} else {
- $res = $this->fetchAssoc('SELECT * FROM `_category` ORDER BY name');
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
- return empty($res) ? [] : self::daoToCategories($res);
+ $res = $this->fetchAssoc('SELECT * FROM `_category` ORDER BY name') ?? [];
+ /** @var list<array{name:string,id:int,kind:int,lastUpdate?:int,error?:int|bool,attributes?:string}> $res */
+ return empty($res) ? [] : self::daoToCategories($res); // @phpstan-ignore varTag.type
}
}
- /** @return array<int,FreshRSS_Category> */
+ /** @return list<FreshRSS_Category> */
public function listCategoriesOrderUpdate(int $defaultCacheDuration = 86400, int $limit = 0): array {
$sql = 'SELECT * FROM `_category` WHERE kind = :kind AND `lastUpdate` < :lu ORDER BY `lastUpdate`'
. ($limit < 1 ? '' : ' LIMIT ' . $limit);
@@ -313,9 +319,12 @@ SQL;
$stm->bindValue(':kind', FreshRSS_Category::KIND_DYNAMIC_OPML, PDO::PARAM_INT) &&
$stm->bindValue(':lu', time() - $defaultCacheDuration, PDO::PARAM_INT) &&
$stm->execute()) {
- return self::daoToCategories($stm->fetchAll(PDO::FETCH_ASSOC));
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ /** @var list<array{name:string,id:int,kind:int,lastUpdate:int,error?:int|bool,attributes?:string}> $res */
+ return self::daoToCategories($res);
} else {
$info = $stm !== false ? $stm->errorInfo() : $this->pdo->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->listCategoriesOrderUpdate($defaultCacheDuration, $limit);
}
@@ -327,10 +336,10 @@ SQL;
public function getDefault(): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE id=:id';
$res = $this->fetchAssoc($sql, [':id' => self::DEFAULTCATEGORYID]) ?? [];
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
- $categories = self::daoToCategories($res);
- if (isset($categories[self::DEFAULTCATEGORYID])) {
- return $categories[self::DEFAULTCATEGORYID];
+ /** @var array<array{name:string,id:int,kind:int,lastUpdate?:int,error?:int|bool,attributes?:string}> $res */
+ $categories = self::daoToCategories($res); // @phpstan-ignore varTag.type
+ if (isset($categories[0])) {
+ return $categories[0];
} else {
if (FreshRSS_Context::$isCli) {
fwrite(STDERR, 'FreshRSS database error: Default category not found!' . "\n");
@@ -388,7 +397,7 @@ SQL;
return isset($res[0]) ? (int)$res[0] : -1;
}
- /** @return array<int,string> */
+ /** @return list<string> */
public function listTitles(int $id, int $limit = 0): array {
$sql = <<<'SQL'
SELECT e.title FROM `_entry` e
@@ -398,15 +407,15 @@ SQL;
SQL;
$sql .= ($limit < 1 ? '' : ' LIMIT ' . intval($limit));
$res = $this->fetchColumn($sql, 0, [':id_category' => $id]) ?? [];
- /** @var array<int,string> $res */
+ /** @var list<string> $res */
return $res;
}
/**
- * @param array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string,
- * 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'website'?:string,'priority'?:int,
- * 'error'?:int|bool,'attributes'?:string,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $listDAO
- * @return array<int,FreshRSS_Category>
+ * @param array<array{c_name:string,c_id:int,c_kind:int,c_last_update:int,c_error:int|bool,c_attributes?:string,
+ * id?:int,name?:string,url?:string,kind?:int,website?:string,priority?:int,
+ * error?:int|bool,attributes?:string,cache_nbEntries?:int,cache_nbUnreads?:int,ttl?:int}> $listDAO
+ * @return list<FreshRSS_Category>
*/
private static function daoToCategoriesPrepopulated(array $listDAO): array {
$list = [];
@@ -414,8 +423,6 @@ SQL;
$feedsDao = [];
$feedDao = FreshRSS_Factory::createFeedDao();
foreach ($listDAO as $line) {
- FreshRSS_DatabaseDAO::pdoInt($line, ['c_id', 'c_kind', 'c_last_update', 'c_error',
- 'id', 'kind', 'priority', 'error', 'cache_nbEntries', 'cache_nbUnreads', 'ttl']);
if (!empty($previousLine['c_id']) && $line['c_id'] !== $previousLine['c_id']) {
// End of the current category, we add it to the $list
$cat = new FreshRSS_Category(
@@ -425,7 +432,7 @@ SQL;
);
$cat->_kind($previousLine['c_kind']);
$cat->_attributes($previousLine['c_attributes'] ?? '[]');
- $list[$cat->id()] = $cat;
+ $list[] = $cat;
$feedsDao = []; //Prepare for next category
}
@@ -445,20 +452,19 @@ SQL;
$cat->_lastUpdate($previousLine['c_last_update'] ?? 0);
$cat->_error($previousLine['c_error'] ?? 0);
$cat->_attributes($previousLine['c_attributes'] ?? []);
- $list[$cat->id()] = $cat;
+ $list[] = $cat;
}
return $list;
}
/**
- * @param array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $listDAO
- * @return array<int,FreshRSS_Category>
+ * @param array<array{name:string,id:int,kind:int,lastUpdate?:int,error?:int|bool,attributes?:string}> $listDAO
+ * @return list<FreshRSS_Category>
*/
private static function daoToCategories(array $listDAO): array {
$list = [];
foreach ($listDAO as $dao) {
- FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'kind', 'lastUpdate', 'error']);
$cat = new FreshRSS_Category(
$dao['name'],
$dao['id']
@@ -467,7 +473,7 @@ SQL;
$cat->_lastUpdate($dao['lastUpdate'] ?? 0);
$cat->_error($dao['error'] ?? 0);
$cat->_attributes($dao['attributes'] ?? '');
- $list[$cat->id()] = $cat;
+ $list[] = $cat;
}
return $list;
}
diff --git a/app/Models/CategoryDAOSQLite.php b/app/Models/CategoryDAOSQLite.php
index d13c52550..f4db76299 100644
--- a/app/Models/CategoryDAOSQLite.php
+++ b/app/Models/CategoryDAOSQLite.php
@@ -3,7 +3,7 @@ declare(strict_types=1);
class FreshRSS_CategoryDAOSQLite extends FreshRSS_CategoryDAO {
- /** @param array<int|string> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
#[\Override]
protected function autoUpdateDb(array $errorInfo): bool {
if (($tableInfo = $this->pdo->query("PRAGMA table_info('category')")) !== false) {
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 6cdda909c..b9cc77498 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -8,11 +8,11 @@ declare(strict_types=1);
final class FreshRSS_Context {
/**
- * @var array<int,FreshRSS_Category>
+ * @var list<FreshRSS_Category>
*/
private static array $categories = [];
/**
- * @var array<int,FreshRSS_Tag>
+ * @var list<FreshRSS_Tag>
*/
private static array $tags = [];
public static string $name = '';
@@ -176,7 +176,7 @@ final class FreshRSS_Context {
FreshRSS_Context::$user_conf = null;
}
- /** @return array<int,FreshRSS_Category> */
+ /** @return list<FreshRSS_Category> */
public static function categories(): array {
if (empty(self::$categories)) {
$catDAO = FreshRSS_Factory::createCategoryDao();
@@ -185,12 +185,12 @@ final class FreshRSS_Context {
return self::$categories;
}
- /** @return array<int,FreshRSS_Feed> */
+ /** @return list<FreshRSS_Feed> */
public static function feeds(): array {
return FreshRSS_Category::findFeeds(self::categories());
}
- /** @return array<int,FreshRSS_Tag> */
+ /** @return list<FreshRSS_Tag> */
public static function labels(bool $precounts = false): array {
if (empty(self::$tags) || $precounts) {
$tagDAO = FreshRSS_Factory::createTagDao();
@@ -429,7 +429,6 @@ final class FreshRSS_Context {
self::$name = _t('index.feed.title_fav');
self::$description = FreshRSS_Context::systemConf()->meta_description;
self::$get_unread = self::$total_starred['unread'];
-
// Update state if favorite is not yet enabled.
self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE;
break;
@@ -437,11 +436,7 @@ final class FreshRSS_Context {
// We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description
$feed = FreshRSS_Context::systemConf()->allow_robots ? null : FreshRSS_Category::findFeed(self::$categories, $id);
if ($feed === null) {
- $feedDAO = FreshRSS_Factory::createFeedDao();
- $feed = $feedDAO->searchById($id);
- if ($feed === null) {
- throw new FreshRSS_Context_Exception('Invalid feed: ' . $id);
- }
+ throw new FreshRSS_Context_Exception('Invalid feed: ' . $id);
}
self::$current_get['feed'] = $id;
self::$current_get['category'] = $feed->categoryId();
@@ -452,15 +447,15 @@ final class FreshRSS_Context {
case 'c':
// We try to find the corresponding category.
self::$current_get['category'] = $id;
- if (!isset(self::$categories[$id])) {
- $catDAO = FreshRSS_Factory::createCategoryDao();
- $cat = $catDAO->searchById($id);
- if ($cat === null) {
- throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
+ $cat = null;
+ foreach (self::$categories as $category) {
+ if ($category->id() === $id) {
+ $cat = $category;
+ break;
}
- self::$categories[$id] = $cat;
- } else {
- $cat = self::$categories[$id];
+ }
+ if ($cat === null) {
+ throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
}
self::$name = $cat->name();
self::$get_unread = $cat->nbNotRead();
@@ -468,15 +463,15 @@ final class FreshRSS_Context {
case 't':
// We try to find the corresponding tag.
self::$current_get['tag'] = $id;
- if (!isset(self::$tags[$id])) {
- $tagDAO = FreshRSS_Factory::createTagDao();
- $tag = $tagDAO->searchById($id);
- if ($tag === null) {
- throw new FreshRSS_Context_Exception('Invalid tag: ' . $id);
+ $tag = null;
+ foreach (self::$tags as $t) {
+ if ($t->id() === $id) {
+ $tag = $t;
+ break;
}
- self::$tags[$id] = $tag;
- } else {
- $tag = self::$tags[$id];
+ }
+ if ($tag === null) {
+ throw new FreshRSS_Context_Exception('Invalid tag: ' . $id);
}
self::$name = $tag->name();
self::$get_unread = $tag->nbUnread();
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php
index c46c91525..5a58ea2ad 100644
--- a/app/Models/DatabaseDAO.php
+++ b/app/Models/DatabaseDAO.php
@@ -25,10 +25,14 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$db = FreshRSS_Context::systemConf()->db;
try {
- $sql = sprintf($GLOBALS['SQL_CREATE_DB'], empty($db['base']) ? '' : $db['base']);
+ $sql = $GLOBALS['SQL_CREATE_DB'];
+ if (!is_string($sql)) {
+ throw new Exception('SQL_CREATE_DB is not a string!');
+ }
+ $sql = sprintf($sql, empty($db['base']) ? '' : $db['base']);
return $this->pdo->exec($sql) === false ? 'Error during CREATE DATABASE' : '';
} catch (Exception $e) {
- syslog(LOG_DEBUG, __method__ . ' notice: ' . $e->getMessage());
+ syslog(LOG_DEBUG, __METHOD__ . ' notice: ' . $e->getMessage());
return $e->getMessage();
}
}
@@ -43,7 +47,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res == false ? 'Error during SQL connection fetch test!' : '';
} catch (Exception $e) {
- syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
+ syslog(LOG_DEBUG, __METHOD__ . ' warning: ' . $e->getMessage());
return $e->getMessage();
}
}
@@ -81,7 +85,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
return count(array_keys($tables, true, true)) === count($tables);
}
- /** @return array<array{name:string,type:string,notnull:bool,default:mixed}> */
+ /** @return list<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);
@@ -164,16 +168,16 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
*/
public function daoToSchema(array $dao): array {
return [
- 'name' => (string)($dao['Field']),
- 'type' => strtolower((string)($dao['Type'])),
- 'notnull' => (bool)$dao['Null'],
- 'default' => $dao['Default'],
+ 'name' => is_string($dao['Field'] ?? null) ? $dao['Field'] : '',
+ 'type' => is_string($dao['Type'] ?? null) ? strtolower($dao['Type']) : '',
+ 'notnull' => empty($dao['Null']),
+ 'default' => is_scalar($dao['Default'] ?? null) ? $dao['Default'] : null,
];
}
/**
* @param array<array<string,string|int|bool|null>> $listDAO
- * @return array<array{name:string,type:string,notnull:bool,default:mixed}>
+ * @return list<array{name:string,type:string,notnull:bool,default:mixed}>
*/
public function listDaoToSchema(array $listDAO): array {
$list = [];
@@ -198,7 +202,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
return self::$staticVersion;
}
static $version = null;
- if ($version === null) {
+ if (!is_string($version)) {
$version = $this->fetchValue('SELECT version()') ?? '';
}
return $version;
@@ -256,7 +260,7 @@ SQL;
$catDAO->resetDefaultCategoryName();
include_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php');
- if (!empty($GLOBALS['SQL_UPDATE_MINOR'])) {
+ if (!empty($GLOBALS['SQL_UPDATE_MINOR']) && is_string($GLOBALS['SQL_UPDATE_MINOR'])) {
$sql = $GLOBALS['SQL_UPDATE_MINOR'];
$isMariaDB = false;
@@ -272,7 +276,7 @@ SQL;
if ($this->pdo->exec($sql) === false) {
$info = $this->pdo->errorInfo();
if ($this->pdo->dbType() === 'mysql' &&
- !$isMariaDB && !empty($info[2]) && (stripos($info[2], "Can't DROP ") !== false)) {
+ !$isMariaDB && is_string($info[2] ?? null) && (stripos($info[2], "Can't DROP ") !== false)) {
// Too bad for MySQL, but ignore error
return;
}
@@ -444,7 +448,7 @@ SQL;
foreach ($tagFrom->selectEntryTag() as $entryTag) {
if (!empty($idMaps['t' . $entryTag['id_tag']])) {
$entryTag['id_tag'] = $idMaps['t' . $entryTag['id_tag']];
- if (!$tagTo->tagEntry($entryTag['id_tag'], $entryTag['id_entry'])) {
+ if (!$tagTo->tagEntry($entryTag['id_tag'], (string)$entryTag['id_entry'])) {
$error = 'Error during SQLite copy of entry-tags!';
return self::stdError($error);
}
@@ -454,31 +458,4 @@ SQL;
return true;
}
-
- /**
- * Ensure that some PDO columns are `int` and not `string`.
- * Compatibility with PHP 7.
- * @param array<string|int|null> $table
- * @param array<string> $columns
- */
- public static function pdoInt(array &$table, array $columns): void {
- foreach ($columns as $column) {
- if (isset($table[$column]) && is_string($table[$column])) {
- $table[$column] = (int)$table[$column];
- }
- }
- }
-
- /**
- * Ensure that some PDO columns are `string` and not `bigint`.
- * @param array<string|int|null> $table
- * @param array<string> $columns
- */
- public static function pdoString(array &$table, array $columns): void {
- foreach ($columns as $column) {
- if (isset($table[$column])) {
- $table[$column] = (string)$table[$column];
- }
- }
- }
}
diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
index 3cce4b062..a183bdee6 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{name:string,type:string,notnull:bool,default:mixed}> */
+ /** @return list<array{name:string,type:string,notnull:bool,default:mixed}> */
#[\Override]
public function getSchema(string $table): array {
$sql = <<<'SQL'
@@ -52,10 +52,10 @@ SQL;
#[\Override]
public function daoToSchema(array $dao): array {
return [
- 'name' => (string)($dao['field']),
- 'type' => strtolower((string)($dao['type'])),
- 'notnull' => (bool)$dao['null'],
- 'default' => $dao['default'],
+ 'name' => is_string($dao['field'] ?? null) ? $dao['field'] : '',
+ 'type' => is_string($dao['type'] ?? null) ? strtolower($dao['type']) : '',
+ 'notnull' => empty($dao['null']),
+ 'default' => is_scalar($dao['default'] ?? null) ? $dao['default'] : null,
];
}
diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php
index 231616f49..f59f6c9ae 100644
--- a/app/Models/DatabaseDAOSQLite.php
+++ b/app/Models/DatabaseDAOSQLite.php
@@ -24,18 +24,25 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
$this->pdo->prefix() . 'entrytag' => false,
];
foreach ($res as $value) {
- $tables[$value['name']] = true;
+ if (is_array($value) && is_string($value['name'] ?? null)) {
+ $tables[$value['name']] = true;
+ }
}
return count(array_keys($tables, true, true)) == count($tables);
}
- /** @return array<array{name:string,type:string,notnull:bool,default:mixed}> */
+ /** @return list<array{name:string,type:string,notnull:bool,default:mixed}> */
#[\Override]
public function getSchema(string $table): array {
$sql = 'PRAGMA table_info(' . $table . ')';
$stm = $this->pdo->query($sql);
- return $stm !== false ? $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
+ if ($stm !== false) {
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ /** @var list<array{name:string,type:string,notnull:bool,dflt_value:string|int|bool|null}> $res */
+ return $this->listDaoToSchema($res ?: []);
+ }
+ return [];
}
#[\Override]
@@ -59,10 +66,10 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
#[\Override]
public function daoToSchema(array $dao): array {
return [
- 'name' => (string)$dao['name'],
- 'type' => strtolower((string)$dao['type']),
- 'notnull' => $dao['notnull'] == '1' ? true : false,
- 'default' => $dao['dflt_value'],
+ 'name' => is_string($dao['name'] ?? null) ? $dao['name'] : '',
+ 'type' => is_string($dao['type'] ?? null) ? strtolower($dao['type']) : '',
+ 'notnull' => empty($dao['notnull']),
+ 'default' => is_scalar($dao['dflt_value'] ?? null) ? $dao['dflt_value'] : null,
];
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 36ed11b40..c32506319 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -52,12 +52,10 @@ class FreshRSS_Entry extends Minz_Model {
$this->_guid($guid);
}
- /** @param array{'id'?:string,'id_feed'?:int,'guid'?:string,'title'?:string,'author'?:string,'content'?:string,'link'?:string,'date'?:int|string,'lastSeen'?:int,
- * 'hash'?:string,'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array<string>,'attributes'?:?string,'thumbnail'?:string,'timestamp'?:string} $dao */
+ /** @param array{id?:string,id_feed?:int,guid?:string,title?:string,author?:string,content?:string,link?:string,date?:int|string,lastSeen?:int,
+ * hash?:string,is_read?:bool|int,is_favorite?:bool|int,tags?:string|array<string>,attributes?:?string,thumbnail?:string,timestamp?:string} $dao */
public static function fromArray(array $dao): FreshRSS_Entry {
- FreshRSS_DatabaseDAO::pdoInt($dao, ['id_feed', 'date', 'lastSeen', 'is_read', 'is_favorite']);
-
- if (empty($dao['content'])) {
+ if (empty($dao['content']) || !is_string($dao['content'])) {
$dao['content'] = '';
}
@@ -83,7 +81,7 @@ class FreshRSS_Entry extends Minz_Model {
$dao['is_favorite'] ?? false,
$dao['tags'] ?? ''
);
- if (!empty($dao['id'])) {
+ if (!empty($dao['id']) && is_numeric($dao['id'])) {
$entry->_id($dao['id']);
}
if (!empty($dao['timestamp'])) {
@@ -241,7 +239,9 @@ HTML;
$content .= '<figure class="enclosure">';
foreach ($thumbnails as $thumbnail) {
- $content .= '<p><img class="enclosure-thumbnail" src="' . $thumbnail . '" alt="" title="' . $etitle . '" /></p>';
+ if (is_string($thumbnail)) {
+ $content .= '<p><img class="enclosure-thumbnail" src="' . $thumbnail . '" alt="" title="' . $etitle . '" /></p>';
+ }
}
if (self::enclosureIsImage($enclosure)) {
@@ -283,9 +283,9 @@ HTML;
/** @return Traversable<array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string|array<string>,'height'?:int,'width'?:int,'thumbnails'?:array<string>}> */
public function enclosures(bool $searchBodyImages = false): Traversable {
$attributeEnclosures = $this->attributeArray('enclosures');
- if (is_iterable($attributeEnclosures)) {
+ if (is_array($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 */
+ /** @var list<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 {
@@ -354,7 +354,7 @@ HTML;
public function thumbnail(bool $searchEnclosures = true): ?array {
$thumbnail = $this->attributeArray('thumbnail') ?? [];
// First, use the provided thumbnail, if any
- if (!empty($thumbnail['url'])) {
+ if (is_string($thumbnail['url'] ?? null)) {
/** @var array{'url':string,'height'?:int,'width'?:int,'time'?:string} $thumbnail */
return $thumbnail;
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 525687c90..4e7f532ac 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -35,7 +35,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return [];
}
- /** @param array<int|string> $values */
+ /** @param list<int|string> $values */
protected static function sqlRegex(string $expression, string $regex, array &$values): string {
// The implementation of this function is solely for MySQL and MariaDB
static $databaseDAOMySQL = null;
@@ -90,7 +90,7 @@ SQL;
$ok = $this->pdo->exec($sql) !== false;
} catch (Exception $e) {
$ok = false;
- Minz_Log::error(__method__ . ' error: ' . $e->getMessage());
+ Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage());
}
return $ok;
}
@@ -99,7 +99,7 @@ SQL;
if ($this->pdo->inTransaction()) {
$this->pdo->commit();
}
- Minz_Log::warning(__method__ . ': ' . $name);
+ Minz_Log::warning(__METHOD__ . ': ' . $name);
try {
if ($name === 'attributes') { //v1.20.0
$sql = <<<'SQL'
@@ -109,13 +109,13 @@ SQL;
return $this->pdo->exec($sql) !== false;
}
} catch (Exception $e) {
- Minz_Log::error(__method__ . ' error: ' . $e->getMessage());
+ Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage());
}
return false;
}
//TODO: Move the database auto-updates to DatabaseDAO
- /** @param array<string|int> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
protected function autoUpdateDb(array $errorInfo): bool {
if (isset($errorInfo[0])) {
if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
@@ -201,6 +201,7 @@ SQL;
return true;
} else {
$info = $this->addEntryPrepared == false ? $this->pdo->errorInfo() : $this->addEntryPrepared->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
$this->addEntryPrepared = null;
return $this->addEntry($valuesTmp);
@@ -310,6 +311,7 @@ SQL;
return true;
} else {
$info = $this->updateEntryPrepared == false ? $this->pdo->errorInfo() : $this->updateEntryPrepared->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->updateEntry($valuesTmp);
}
@@ -336,7 +338,7 @@ SQL;
* @todo simplify the query by removing the str_repeat. I am pretty sure
* there is an other way to do that.
*
- * @param numeric-string|array<numeric-string> $ids
+ * @param numeric-string|list<numeric-string> $ids
*/
public function markFavorite($ids, bool $is_favorite = true): int|false {
if (!is_array($ids)) {
@@ -414,7 +416,7 @@ SQL;
* Toggle the read marker on one or more article.
* Then the cache is updated.
*
- * @param numeric-string|array<numeric-string> $ids
+ * @param numeric-string|list<numeric-string> $ids
* @return int|false affected rows
*/
public function markRead(array|string $ids, bool $is_read = true): int|false {
@@ -720,16 +722,17 @@ SQL;
return $stm->rowCount();
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->cleanOldEntries($id_feed, $options);
}
- Minz_Log::error(__method__ . ' error:' . json_encode($info));
+ Minz_Log::error(__METHOD__ . ' error:' . json_encode($info));
return false;
}
}
- /** @return Traversable<array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,
- * 'hash':string,'is_read':bool,'is_favorite':bool,'id_feed':int,'tags':string,'attributes':?string}> */
+ /** @return Traversable<array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,lastSeen:int,
+ * hash:string,is_read:bool,is_favorite:bool,id_feed:int,tags:string,attributes:?string}> */
public function selectAll(?int $limit = null): Traversable {
$content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
$hash = static::sqlHexEncode('hash');
@@ -743,16 +746,17 @@ SQL;
$stm = $this->pdo->query($sql);
if ($stm != false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,
- * 'hash':string,'is_read':bool,'is_favorite':bool,'id_feed':int,'tags':string,'attributes':?string} $row */
+ /** @var array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,lastSeen:int,
+ * hash:string,is_read:bool,is_favorite:bool,id_feed:int,tags:string,attributes:?string} $row */
yield $row;
}
} else {
$info = $this->pdo->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
yield from $this->selectAll();
} else {
- Minz_Log::error(__method__ . ' error: ' . json_encode($info));
+ Minz_Log::error(__METHOD__ . ' error: ' . json_encode($info));
}
}
}
@@ -765,8 +769,8 @@ SELECT id, guid, title, author, link, date, is_read, is_favorite, {$hash} AS has
FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid
SQL;
$res = $this->fetchAssoc($sql, [':id_feed' => $id_feed, ':guid' => $guid]);
- /** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'is_read':int,'is_favorite':int,'tags':string,'attributes':?string}> $res */
+ /** @var list<array{id:string,id_feed:int,guid:string,title:string,author:string,content:string,link:string,date:int,
+ * is_read:int,is_favorite:int,tags:string,attributes:?string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
@@ -778,7 +782,7 @@ SELECT id, guid, title, author, link, date, is_read, is_favorite, {$hash} AS has
FROM `_entry` WHERE id=:id
SQL;
$res = $this->fetchAssoc($sql, [':id' => $id]);
- /** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ /** @var list<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
* 'is_read':int,'is_favorite':int,'tags':string,'attributes':?string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
@@ -789,7 +793,7 @@ SQL;
return empty($res[0]) ? null : (string)($res[0]);
}
- /** @return array{0:array<int|string>,1:string} */
+ /** @return array{0:list<int|string>,1:string} */
public static function sqlBooleanSearch(string $alias, FreshRSS_BooleanSearch $filters, int $level = 0): array {
$search = '';
$values = [];
@@ -1104,7 +1108,7 @@ SQL;
/**
* @param 'ASC'|'DESC' $order
- * @return array{0:array<int|string>,1:string}
+ * @return array{0:list<int|string>,1:string}
* @throws FreshRSS_EntriesGetter_Exception
*/
protected function sqlListEntriesWhere(string $alias = '', ?FreshRSS_BooleanSearch $filters = null,
@@ -1173,7 +1177,7 @@ SQL;
* @phpstan-param 'a'|'A'|'i'|'s'|'S'|'c'|'f'|'t'|'T'|'ST'|'Z' $type
* @param int $id category/feed/tag ID
* @param 'ASC'|'DESC' $order
- * @return array{0:array<int|string>,1:string}
+ * @return array{0:list<int|string>,1:string}
* @throws FreshRSS_EntriesGetter_Exception
*/
private function sqlListWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
@@ -1269,6 +1273,7 @@ SQL;
return $stm;
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->listWhereRaw($type, $id, $state, $order, $limit, $offset, $firstId, $filters, $date_min);
}
@@ -1347,7 +1352,7 @@ SQL;
* @phpstan-param 'a'|'A'|'s'|'S'|'c'|'f'|'t'|'T'|'ST'|'Z' $type
* @param int $id category/feed/tag ID
* @param 'ASC'|'DESC' $order
- * @return array<numeric-string>|null
+ * @return list<numeric-string>|null
* @throws FreshRSS_EntriesGetter_Exception
*/
public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
@@ -1356,7 +1361,8 @@ SQL;
[$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $offset, $firstId, $filters);
$stm = $this->pdo->prepare($sql);
if ($stm !== false && $stm->execute($values) && ($res = $stm->fetchAll(PDO::FETCH_COLUMN, 0)) !== false) {
- /** @var array<numeric-string> $res */
+ $res = array_map('strval', $res);
+ /** @var list<numeric-string> $res */
return $res;
}
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
@@ -1366,7 +1372,7 @@ SQL;
/**
* @param array<string> $guids
- * @return array<string>|false
+ * @return array<string,string>|false
*/
public function listHashForFeedGuids(int $id_feed, array $guids): array|false {
$result = [];
@@ -1376,7 +1382,7 @@ SQL;
// Split a query with too many variables parameters
$guidsChunks = array_chunk($guids, FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER);
foreach ($guidsChunks as $guidsChunk) {
- $result += $this->listHashForFeedGuids($id_feed, $guidsChunk);
+ $result += $this->listHashForFeedGuids($id_feed, $guidsChunk) ?: [];
}
return $result;
}
@@ -1394,9 +1400,6 @@ SQL;
return $result;
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
- if ($this->autoUpdateDb($info)) {
- return $this->listHashForFeedGuids($id_feed, $guids);
- }
Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while querying feed ' . $id_feed);
return false;
@@ -1430,9 +1433,6 @@ SQL;
return $stm->rowCount();
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
- if ($this->autoUpdateDb($info)) {
- return $this->updateLastSeen($id_feed, $guids);
- }
Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)
. ' while updating feed ' . $id_feed);
return false;
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index fe157308c..1a5266bbd 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -49,7 +49,7 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
// Nothing to do for PostgreSQL
}
- /** @param array<string|int> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
#[\Override]
protected function autoUpdateDb(array $errorInfo): bool {
if (isset($errorInfo[0])) {
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 7cf6eb202..5734ec3b3 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -49,7 +49,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
);
}
- /** @param array<string|int> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
#[\Override]
protected function autoUpdateDb(array $errorInfo): bool {
if (($tableInfo = $this->pdo->query("PRAGMA table_info('entry')")) !== false) {
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 841749312..645dbcf3c 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -122,13 +122,13 @@ class FreshRSS_Feed extends Minz_Model {
}
/**
- * @return array<FreshRSS_Entry>|null
+ * @return list<FreshRSS_Entry>|null
* @deprecated
*/
public function entries(): ?array {
- Minz_Log::warning(__method__ . ' is deprecated since FreshRSS 1.16.1!');
+ Minz_Log::warning(__METHOD__ . ' is deprecated since FreshRSS 1.16.1!');
$simplePie = $this->load(false, true);
- return $simplePie == null ? [] : iterator_to_array($this->loadEntries($simplePie));
+ return $simplePie == null ? [] : array_values(iterator_to_array($this->loadEntries($simplePie)));
}
public function name(bool $raw = false): string {
return $raw || $this->name != '' ? $this->name : (preg_replace('%^https?://(www[.])?%i', '', $this->url) ?? '');
@@ -479,7 +479,7 @@ class FreshRSS_Feed extends Minz_Model {
* @param float $invalidGuidsTolerance (default 0.05) The maximum ratio (rounded) of invalid GUIDs to tolerate before degrading the unicity criteria.
* Example for 0.05 (5% rounded): tolerate 0 invalid GUIDs for up to 9 articles, 1 for 10, 2 for 30, 3 for 50, 4 for 70, 5 for 90, 6 for 110, etc.
* The default value of 5% rounded was chosen to allow 1 invalid GUID for feeds of 10 articles, which is a frequently observed amount of articles.
- * @return array<string>
+ * @return list<string>
*/
public function loadGuids(\SimplePie\SimplePie $simplePie, float $invalidGuidsTolerance = 0.05): array {
$invalidGuids = 0;
@@ -1077,13 +1077,13 @@ class FreshRSS_Feed extends Minz_Model {
$hubFilename = $path . '/!hub.json';
if (($hubFile = @file_get_contents($hubFilename)) != false) {
$hubJson = json_decode($hubFile, true);
- if (!is_array($hubJson) || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
+ if (!is_array($hubJson) || empty($hubJson['key']) || !is_string($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
$text = 'Invalid JSON for WebSub: ' . $this->url;
Minz_Log::warning($text);
Minz_Log::warning($text, PSHB_LOG);
return false;
}
- if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy
+ if (!empty($hubJson['lease_end']) && is_int($hubJson['lease_end']) && $hubJson['lease_end'] < (time() + (3600 * 23))) { //TODO: Make a better policy
$text = 'WebSub lease ends at '
. date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end'])
. ' and needs renewal: ' . $this->url;
@@ -1131,7 +1131,8 @@ class FreshRSS_Feed extends Minz_Model {
return false;
}
$hubJson = json_decode($hubFile, true);
- if (!is_array($hubJson) || empty($hubJson['key']) || !ctype_xdigit($hubJson['key']) || empty($hubJson['hub'])) {
+ if (!is_array($hubJson) || empty($hubJson['key']) || !is_string($hubJson['key']) || !ctype_xdigit($hubJson['key']) ||
+ empty($hubJson['hub']) || !is_string($hubJson['hub'])) {
Minz_Log::warning('Invalid JSON for WebSub: ' . $this->url);
return false;
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index fa52838ca..676b93b7f 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -7,18 +7,18 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if ($this->pdo->inTransaction()) {
$this->pdo->commit();
}
- Minz_Log::warning(__method__ . ': ' . $name);
+ Minz_Log::warning(__METHOD__ . ': ' . $name);
try {
if ($name === 'kind') { //v1.20.0
return $this->pdo->exec('ALTER TABLE `_feed` ADD COLUMN kind SMALLINT DEFAULT 0') !== false;
}
} catch (Exception $e) {
- Minz_Log::error(__method__ . ' error: ' . $e->getMessage());
+ Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage());
}
return false;
}
- /** @param array<int|string> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
protected function autoUpdateDb(array $errorInfo): bool {
if (isset($errorInfo[0])) {
if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
@@ -34,8 +34,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
/**
- * @param array{'url':string,'kind':int,'category':int,'name':string,'website':string,'description':string,'lastUpdate':int,'priority'?:int,
- * 'pathEntries'?:string,'httpAuth':string,'error':int|bool,'ttl'?:int,'attributes'?:string|array<string|mixed>} $valuesTmp
+ * @param array{url:string,kind:int,category:int,name:string,website:string,description:string,lastUpdate:int,priority?:int,
+ * pathEntries?:string,httpAuth:string,error:int|bool,ttl?:int,attributes?:string|array<string|mixed>} $valuesTmp
*/
public function addFeed(array $valuesTmp): int|false {
$sql = 'INSERT INTO `_feed` (url, kind, category, name, website, description, `lastUpdate`, priority, `pathEntries`, `httpAuth`, error, ttl, attributes)
@@ -72,6 +72,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return $feedId === false ? false : (int)$feedId;
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->addFeed($valuesTmp);
}
@@ -177,6 +178,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return true;
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->updateFeed($id, $originalValues);
}
@@ -290,8 +292,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
}
- /** @return Traversable<array{'id':int,'url':string,'kind':int,'category':int,'name':string,'website':string,'description':string,'lastUpdate':int,'priority'?:int,
- * 'pathEntries'?:string,'httpAuth':string,'error':int|bool,'ttl'?:int,'attributes'?:string}> */
+ /** @return Traversable<array{id:int,url:string,kind:int,category:int,name:string,website:string,description:string,lastUpdate:int,priority?:int,
+ * pathEntries?:string,httpAuth:string,error:int|bool,ttl?:int,attributes?:string}> */
public function selectAll(): Traversable {
$sql = <<<'SQL'
SELECT id, url, kind, category, name, website, description, `lastUpdate`,
@@ -301,16 +303,17 @@ SQL;
$stm = $this->pdo->query($sql);
if ($stm !== false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':int,'url':string,'kind':int,'category':int,'name':string,'website':string,'description':string,'lastUpdate':int,'priority'?:int,
- * 'pathEntries'?:string,'httpAuth':string,'error':int|bool,'ttl'?:int,'attributes'?:string} $row */
+ /** @var array{id:int,url:string,kind:int,category:int,name:string,website:string,description:string,lastUpdate:int,priority?:int,
+ * pathEntries?:string,httpAuth:string,error:int|bool,ttl?:int,attributes?:string} $row */
yield $row;
}
} else {
$info = $this->pdo->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
yield from $this->selectAll();
} else {
- Minz_Log::error(__method__ . ' error: ' . json_encode($info));
+ Minz_Log::error(__METHOD__ . ' error: ' . json_encode($info));
}
}
}
@@ -318,40 +321,34 @@ SQL;
public function searchById(int $id): ?FreshRSS_Feed {
$sql = 'SELECT * FROM `_feed` WHERE id=:id';
$res = $this->fetchAssoc($sql, [':id' => $id]);
- if ($res == null) {
+ if (!is_array($res)) {
return null;
}
- /** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */
- $feeds = self::daoToFeeds($res);
- return $feeds[$id] ?? null;
+ $feeds = self::daoToFeeds($res); // @phpstan-ignore argument.type
+ return $feeds[0] ?? null;
}
public function searchByUrl(string $url): ?FreshRSS_Feed {
$sql = 'SELECT * FROM `_feed` WHERE url=:url';
$res = $this->fetchAssoc($sql, [':url' => $url]);
- /** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */
- return empty($res[0]) ? null : (current(self::daoToFeeds($res)) ?: null);
+ return empty($res[0]) ? null : (current(self::daoToFeeds($res)) ?: null); // @phpstan-ignore argument.type
}
- /** @return array<int> */
+ /** @return list<int> */
public function listFeedsIds(): array {
$sql = 'SELECT id FROM `_feed`';
- /** @var array<int> $res */
+ /** @var list<int> $res */
$res = $this->fetchColumn($sql, 0) ?? [];
return $res;
}
/**
- * @return array<int,FreshRSS_Feed>
+ * @return list<FreshRSS_Feed>
*/
public function listFeeds(): array {
$sql = 'SELECT * FROM `_feed` ORDER BY name';
$res = $this->fetchAssoc($sql);
- /** @var array<array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'ttl':int,'attributes':string}>|null $res */
- return $res == null ? [] : self::daoToFeeds($res);
+ return $res == null ? [] : self::daoToFeeds($res); // @phpstan-ignore argument.type
}
/** @return array<string,string> */
@@ -363,7 +360,7 @@ SQL;
$sql .= 'WHERE id_feed=' . intval($id_feed);
}
$res = $this->fetchAssoc($sql);
- /** @var array<array{'id_feed':int,'newest_item_us':string}>|null $res */
+ /** @var list<array{'id_feed':int,'newest_item_us':string}>|null $res */
if ($res == null) {
return [];
}
@@ -376,7 +373,7 @@ SQL;
/**
* @param int $defaultCacheDuration Use -1 to return all feeds, without filtering them by TTL.
- * @return array<int,FreshRSS_Feed>
+ * @return list<FreshRSS_Feed>
*/
public function listFeedsOrderUpdate(int $defaultCacheDuration = 3600, int $limit = 0): array {
$sql = 'SELECT id, url, kind, category, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes, `cache_nbEntries`, `cache_nbUnreads` '
@@ -391,6 +388,7 @@ SQL;
return self::daoToFeeds($stm->fetchAll(PDO::FETCH_ASSOC));
} else {
$info = $this->pdo->errorInfo();
+ /** @var array{0:string,1:int,2:string} $info */
if ($this->autoUpdateDb($info)) {
return $this->listFeedsOrderUpdate($defaultCacheDuration, $limit);
}
@@ -399,19 +397,19 @@ SQL;
}
}
- /** @return array<int,string> */
+ /** @return list<string> */
public function listTitles(int $id, int $limit = 0): array {
$sql = 'SELECT title FROM `_entry` WHERE id_feed=:id_feed ORDER BY id DESC'
. ($limit < 1 ? '' : ' LIMIT ' . intval($limit));
$res = $this->fetchColumn($sql, 0, [':id_feed' => $id]) ?? [];
- /** @var array<int,string> $res */
+ /** @var list<string> $res */
return $res;
}
/**
* @param bool|null $muted to include only muted feeds
* @param bool|null $errored to include only errored feeds
- * @return array<int,FreshRSS_Feed>
+ * @return list<FreshRSS_Feed>
*/
public function listByCategory(int $cat, ?bool $muted = null, ?bool $errored = null): array {
$sql = 'SELECT * FROM `_feed` WHERE category=:category';
@@ -422,18 +420,11 @@ SQL;
$sql .= ' AND error <> 0';
}
$res = $this->fetchAssoc($sql, [':category' => $cat]);
- if ($res == null) {
+ if (!is_array($res)) {
return [];
}
-
- /**
- * @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res
- */
- $feeds = self::daoToFeeds($res);
-
- uasort($feeds, static fn(FreshRSS_Feed $a, FreshRSS_Feed $b) => strnatcasecmp($a->name(), $b->name()));
-
+ $feeds = self::daoToFeeds($res); // @phpstan-ignore argument.type
+ usort($feeds, static fn(FreshRSS_Feed $a, FreshRSS_Feed $b) => strnatcasecmp($a->name(), $b->name()));
return $feeds;
}
@@ -576,23 +567,19 @@ SQL;
}
/**
- * @param array<int,array{'id'?:int,'url'?:string,'kind'?:int,'category'?:int,'name'?:string,'website'?:string,'description'?:string,'lastUpdate'?:int,'priority'?:int,
- * 'pathEntries'?:string,'httpAuth'?:string,'error'?:int|bool,'ttl'?:int,'attributes'?:string,'cache_nbUnreads'?:int,'cache_nbEntries'?:int}> $listDAO
- * @return array<int,FreshRSS_Feed>
+ * @param array<array{id?:int,url?:string,kind?:int,category?:int,name?:string,website?:string,description?:string,lastUpdate?:int,priority?:int,
+ * pathEntries?:string,httpAuth?:string,error?:int|bool,ttl?:int,attributes?:string,cache_nbUnreads?:int,cache_nbEntries?:int}> $listDAO
+ * @return list<FreshRSS_Feed>
*/
public static function daoToFeeds(array $listDAO, ?int $catID = null): array {
$list = [];
- foreach ($listDAO as $key => $dao) {
- FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'kind', 'category', 'lastUpdate', 'priority', 'error', 'ttl', 'cache_nbUnreads', 'cache_nbEntries']);
- if (!isset($dao['name'])) {
+ foreach ($listDAO as $dao) {
+ if (!is_string($dao['name'] ?? null)) {
continue;
}
- if (isset($dao['id'])) {
- $key = (int)$dao['id'];
- }
if ($catID === null) {
- $category = $dao['category'] ?? 0;
+ $category = is_numeric($dao['category'] ?? null) ? (int)$dao['category'] : 0;
} else {
$category = $catID;
}
@@ -615,7 +602,7 @@ SQL;
if (isset($dao['id'])) {
$myFeed->_id($dao['id']);
}
- $list[$key] = $myFeed;
+ $list[] = $myFeed;
}
return $list;
diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php
index 5833a7985..42915a493 100644
--- a/app/Models/FeedDAOSQLite.php
+++ b/app/Models/FeedDAOSQLite.php
@@ -3,7 +3,7 @@ declare(strict_types=1);
class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
- /** @param array<int|string> $errorInfo */
+ /** @param array{0:string,1:int,2:string} $errorInfo */
#[\Override]
protected function autoUpdateDb(array $errorInfo): bool {
if (($tableInfo = $this->pdo->query("PRAGMA table_info('feed')")) !== false) {
diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php
index eb8ea8502..56c182904 100644
--- a/app/Models/FilterAction.php
+++ b/app/Models/FilterAction.php
@@ -3,7 +3,7 @@ declare(strict_types=1);
class FreshRSS_FilterAction {
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $actions = null;
/** @param array<string> $actions */
@@ -15,7 +15,7 @@ class FreshRSS_FilterAction {
return $this->booleanSearch;
}
- /** @return array<string> */
+ /** @return list<string> */
public function actions(): array {
return $this->actions ?? [];
}
@@ -23,7 +23,7 @@ class FreshRSS_FilterAction {
/** @param array<string> $actions */
public function _actions(?array $actions): void {
if (is_array($actions)) {
- $this->actions = array_unique($actions);
+ $this->actions = array_values(array_unique($actions));
} else {
$this->actions = null;
}
@@ -42,7 +42,8 @@ class FreshRSS_FilterAction {
/** @param array|mixed|null $json */
public static function fromJSON($json): ?FreshRSS_FilterAction {
- if (is_array($json) && !empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) {
+ if (is_array($json) && !empty($json['search']) && is_string($json['search']) &&
+ !empty($json['actions']) && is_array($json['actions']) && is_array_values_string($json['actions'])) {
return new FreshRSS_FilterAction(new FreshRSS_BooleanSearch($json['search']), $json['actions']);
}
return null;
diff --git a/app/Models/FilterActionsTrait.php b/app/Models/FilterActionsTrait.php
index 9b7ee66d4..3d8257e34 100644
--- a/app/Models/FilterActionsTrait.php
+++ b/app/Models/FilterActionsTrait.php
@@ -6,11 +6,11 @@ declare(strict_types=1);
*/
trait FreshRSS_FilterActionsTrait {
- /** @var array<FreshRSS_FilterAction>|null $filterActions */
+ /** @var list<FreshRSS_FilterAction>|null $filterActions */
private ?array $filterActions = null;
/**
- * @return array<FreshRSS_FilterAction>
+ * @return list<FreshRSS_FilterAction>
*/
private function filterActions(): array {
if (empty($this->filterActions)) {
@@ -30,7 +30,7 @@ trait FreshRSS_FilterActionsTrait {
* @param array<FreshRSS_FilterAction>|null $filterActions
*/
private function _filterActions(?array $filterActions): void {
- $this->filterActions = $filterActions;
+ $this->filterActions = is_array($filterActions) ? array_values($filterActions) : null;
if ($this->filterActions !== null && !empty($this->filterActions)) {
$this->_attribute('filters', array_map(
static fn(?FreshRSS_FilterAction $af) => $af == null ? null : $af->toJSON(),
@@ -40,7 +40,7 @@ trait FreshRSS_FilterActionsTrait {
}
}
- /** @return array<FreshRSS_BooleanSearch> */
+ /** @return list<FreshRSS_BooleanSearch> */
public function filtersAction(string $action): array {
$action = trim($action);
if ($action == '') {
@@ -121,6 +121,7 @@ trait FreshRSS_FilterActionsTrait {
/**
* @param bool $applyLabel Parameter by reference, which will be set to true if the callers needs to apply a label to the article entry.
+ * @param-out bool $applyLabel
*/
public function applyFilterActions(FreshRSS_Entry $entry, ?bool &$applyLabel = null): void {
$applyLabel = false;
diff --git a/app/Models/FormAuth.php b/app/Models/FormAuth.php
index 54b468da9..1da03f6d2 100644
--- a/app/Models/FormAuth.php
+++ b/app/Models/FormAuth.php
@@ -14,7 +14,7 @@ class FreshRSS_FormAuth {
return password_verify($nonce . $hash, $challenge);
}
- /** @return array<string> */
+ /** @return list<string> */
public static function getCredentialsFromCookie(): array {
$token = Minz_Session::getLongTermCookie('FreshRSS_login');
if (!ctype_alnum($token)) {
diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php
index 3916d2a1e..44cce3ecd 100644
--- a/app/Models/LogDAO.php
+++ b/app/Models/LogDAO.php
@@ -9,7 +9,7 @@ final class FreshRSS_LogDAO {
return USERS_PATH . '/' . (Minz_User::name() ?? Minz_User::INTERNAL_USER) . '/' . $logFileName;
}
- /** @return array<FreshRSS_Log> */
+ /** @return list<FreshRSS_Log> */
public static function lines(?string $logFileName = null): array {
$logs = [];
$handle = @fopen(self::logPath($logFileName), 'r');
diff --git a/app/Models/ReadingMode.php b/app/Models/ReadingMode.php
index 60c7e76e1..01edc6a4c 100644
--- a/app/Models/ReadingMode.php
+++ b/app/Models/ReadingMode.php
@@ -59,7 +59,7 @@ class FreshRSS_ReadingMode {
}
/**
- * @return array<FreshRSS_ReadingMode> the built-in reading modes
+ * @return list<FreshRSS_ReadingMode> the built-in reading modes
*/
public static function getReadingModes(): array {
$actualView = Minz_Request::actionName();
diff --git a/app/Models/Search.php b/app/Models/Search.php
index a887ec2f7..3eb8b422a 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -17,17 +17,17 @@ class FreshRSS_Search implements \Stringable {
private string $raw_input = '';
// The following properties are extracted from the raw input
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $entry_ids = null;
- /** @var array<int>|null */
+ /** @var list<int>|null */
private ?array $feed_ids = null;
- /** @var array<int>|'*'|null */
+ /** @var list<int>|'*'|null */
private $label_ids = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $label_names = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $intitle = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $intitle_regex = null;
/** @var int|false|null */
private $min_date = null;
@@ -37,34 +37,34 @@ class FreshRSS_Search implements \Stringable {
private $min_pubdate = null;
/** @var int|false|null */
private $max_pubdate = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $inurl = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $inurl_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $author = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $author_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $tags = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $tags_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $search = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $search_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_entry_ids = null;
- /** @var array<int>|null */
+ /** @var list<int>|null */
private ?array $not_feed_ids = null;
- /** @var array<int>|'*'|null */
+ /** @var list<int>|'*'|null */
private $not_label_ids = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_label_names = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_intitle = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_intitle_regex = null;
/** @var int|false|null */
private $not_min_date = null;
@@ -74,21 +74,21 @@ class FreshRSS_Search implements \Stringable {
private $not_min_pubdate = null;
/** @var int|false|null */
private $not_max_pubdate = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_inurl = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_inurl_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_author = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_author_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_tags = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_tags_regex = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_search = null;
- /** @var array<string>|null */
+ /** @var list<string>|null */
private ?array $not_search_regex = null;
public function __construct(string $input) {
@@ -137,54 +137,54 @@ class FreshRSS_Search implements \Stringable {
return $this->raw_input;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getEntryIds(): ?array {
return $this->entry_ids;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotEntryIds(): ?array {
return $this->not_entry_ids;
}
- /** @return array<int>|null */
+ /** @return list<int>|null */
public function getFeedIds(): ?array {
return $this->feed_ids;
}
- /** @return array<int>|null */
+ /** @return list<int>|null */
public function getNotFeedIds(): ?array {
return $this->not_feed_ids;
}
- /** @return array<int>|'*'|null */
+ /** @return list<int>|'*'|null */
public function getLabelIds(): array|string|null {
return $this->label_ids;
}
- /** @return array<int>|'*'|null */
+ /** @return list<int>|'*'|null */
public function getNotLabelIds(): array|string|null {
return $this->not_label_ids;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getLabelNames(): ?array {
return $this->label_names;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotLabelNames(): ?array {
return $this->not_label_names;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getIntitle(): ?array {
return $this->intitle;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getIntitleRegex(): ?array {
return $this->intitle_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotIntitle(): ?array {
return $this->not_intitle;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotIntitleRegex(): ?array {
return $this->not_intitle_regex;
}
@@ -223,90 +223,90 @@ class FreshRSS_Search implements \Stringable {
return $this->not_max_pubdate ?: null;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getInurl(): ?array {
return $this->inurl;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getInurlRegex(): ?array {
return $this->inurl_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotInurl(): ?array {
return $this->not_inurl;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotInurlRegex(): ?array {
return $this->not_inurl_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getAuthor(): ?array {
return $this->author;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getAuthorRegex(): ?array {
return $this->author_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotAuthor(): ?array {
return $this->not_author;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotAuthorRegex(): ?array {
return $this->not_author_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getTags(): ?array {
return $this->tags;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getTagsRegex(): ?array {
return $this->tags_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotTags(): ?array {
return $this->not_tags;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotTagsRegex(): ?array {
return $this->not_tags_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getSearch(): ?array {
return $this->search;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getSearchRegex(): ?array {
return $this->search_regex;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotSearch(): ?array {
return $this->not_search;
}
- /** @return array<string>|null */
+ /** @return list<string>|null */
public function getNotSearchRegex(): ?array {
return $this->not_search_regex;
}
/**
- * @param array<string>|null $anArray
- * @return array<string>
+ * @param list<string>|null $anArray
+ * @return list<string>
*/
private static function removeEmptyValues(?array $anArray): array {
- return empty($anArray) ? [] : array_filter($anArray, static fn(string $value) => $value !== '');
+ return empty($anArray) ? [] : array_values(array_filter($anArray, static fn(string $value) => $value !== ''));
}
/**
- * @param array<string>|string $value
- * @return ($value is array ? array<string> : string)
+ * @param list<string>|string $value
+ * @return ($value is string ? string : list<string>)
*/
- private static function decodeSpaces($value): array|string {
+ private static function decodeSpaces(array|string $value): array|string {
if (is_array($value)) {
- for ($i = count($value) - 1; $i >= 0; $i--) {
- $value[$i] = self::decodeSpaces($value[$i]);
+ foreach ($value as &$val) {
+ $val = self::decodeSpaces($val);
}
} else {
$value = trim(str_replace('+', ' ', $value));
@@ -315,8 +315,8 @@ class FreshRSS_Search implements \Stringable {
}
/**
- * @param array<string> $strings
- * @return array<string>
+ * @param list<string> $strings
+ * @return list<string>
*/
private static function htmlspecialchars_decodes(array $strings): array {
return array_map(static fn(string $s) => htmlspecialchars_decode($s, ENT_QUOTES), $strings);
@@ -365,7 +365,7 @@ class FreshRSS_Search implements \Stringable {
foreach ($ids_lists as $ids_list) {
$feed_ids = explode(',', $ids_list);
$feed_ids = self::removeEmptyValues($feed_ids);
- /** @var array<int> $feed_ids */
+ /** @var list<int> $feed_ids */
$feed_ids = array_map('intval', $feed_ids);
if (!empty($feed_ids)) {
$this->feed_ids = array_merge($this->feed_ids, $feed_ids);
@@ -383,7 +383,7 @@ class FreshRSS_Search implements \Stringable {
foreach ($ids_lists as $ids_list) {
$feed_ids = explode(',', $ids_list);
$feed_ids = self::removeEmptyValues($feed_ids);
- /** @var array<int> $feed_ids */
+ /** @var list<int> $feed_ids */
$feed_ids = array_map('intval', $feed_ids);
if (!empty($feed_ids)) {
$this->not_feed_ids = array_merge($this->not_feed_ids, $feed_ids);
@@ -408,7 +408,7 @@ class FreshRSS_Search implements \Stringable {
}
$label_ids = explode(',', $ids_list);
$label_ids = self::removeEmptyValues($label_ids);
- /** @var array<int> $label_ids */
+ /** @var list<int> $label_ids */
$label_ids = array_map('intval', $label_ids);
if (!empty($label_ids)) {
$this->label_ids = array_merge($this->label_ids, $label_ids);
@@ -430,7 +430,7 @@ class FreshRSS_Search implements \Stringable {
}
$label_ids = explode(',', $ids_list);
$label_ids = self::removeEmptyValues($label_ids);
- /** @var array<int> $label_ids */
+ /** @var list<int> $label_ids */
$label_ids = array_map('intval', $label_ids);
if (!empty($label_ids)) {
$this->not_label_ids = array_merge($this->not_label_ids, $label_ids);
diff --git a/app/Models/Share.php b/app/Models/Share.php
index 847127466..140ca0eca 100644
--- a/app/Models/Share.php
+++ b/app/Models/Share.php
@@ -13,8 +13,8 @@ class FreshRSS_Share {
/**
* Register a new sharing option.
- * @param array{'type':string,'url':string,'transform'?:array<callable>|array<string,array<callable>>,'field'?:string,'help'?:string,'form'?:'simple'|'advanced',
- * 'method'?:'GET'|'POST','HTMLtag'?:'button','deprecated'?:bool} $share_options is an array defining the share option.
+ * @param array{type:string,url:string,transform?:array<callable>|array<string,array<callable>>,field?:string,help?:string,form?:'simple'|'advanced',
+ * method?:'GET'|'POST',HTMLtag?:'button',deprecated?:bool} $share_options is an array defining the share option.
*/
public static function register(array $share_options): void {
$type = $share_options['type'];
@@ -46,7 +46,12 @@ class FreshRSS_Share {
}
foreach ($shares_from_file as $share_type => $share_options) {
+ if (!is_array($share_options)) {
+ continue;
+ }
$share_options['type'] = $share_type;
+ /** @var array{type:string,url:string,transform?:array<callable>|array<string,array<callable>>,field?:string,help?:string,form?:'simple'|'advanced',
+ * method?:'GET'|'POST',HTMLtag?:'button',deprecated?:bool} $share_options */
self::register($share_options);
}
@@ -233,8 +238,8 @@ class FreshRSS_Share {
'~LINK~',
];
$replaces = [
- $this->id(),
- $this->base_url,
+ $this->id() ?? '',
+ $this->base_url ?? '',
$this->title(),
$this->link(),
];
@@ -298,7 +303,10 @@ class FreshRSS_Share {
}
foreach ($transform as $action) {
- $data = call_user_func($action, $data);
+ $return = call_user_func($action, $data);
+ if (is_string($return)) {
+ $data = $return;
+ }
}
return $data;
@@ -307,7 +315,7 @@ class FreshRSS_Share {
/**
* Get the list of transformations for the given attribute.
* @param string $attr the attribute of which we want the transformations.
- * @return array<callable> containing a list of transformations to apply.
+ * @return list<callable> containing a list of transformations to apply.
*/
private function getTransform(string $attr): array {
if (array_key_exists($attr, $this->transforms)) {
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 6782bd7ee..d098b81a4 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -29,7 +29,7 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
* - unread entries
* - favorite entries
*
- * @return array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false
+ * @return array{total:int,count_unreads:int,count_reads:int,count_favorites:int}|false
*/
public function calculateEntryRepartitionPerFeed(?int $feed = null, bool $only_main = false): array|false {
$filter = '';
@@ -49,10 +49,9 @@ WHERE e.id_feed = f.id
{$filter}
SQL;
$res = $this->fetchAssoc($sql);
- if (!empty($res[0])) {
+ if (is_array($res) && !empty($res[0])) {
$dao = $res[0];
- /** @var array<array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}> $res */
- FreshRSS_DatabaseDAO::pdoInt($dao, ['total', 'count_unreads', 'count_reads', 'count_favorites']);
+ /** @var array{total:int,count_unreads:int,count_reads:int,count_favorites:int} $dao */
return $dao;
}
return false;
@@ -78,10 +77,10 @@ GROUP BY day
ORDER BY day ASC
SQL;
$res = $this->fetchAssoc($sql);
- if ($res == false) {
+ if (!is_array($res)) {
return [];
}
- /** @var array<array{'day':int,'count':int}> $res */
+ /** @var list<array{day:int,count:int}> $res */
foreach ($res as $value) {
$count[(int)($value['day'])] = (int)($value['count']);
}
@@ -123,7 +122,6 @@ SQL;
return $monthRepartition;
}
-
/**
* Calculates the number of article per period per feed
* @param string $period format string to use for grouping
@@ -228,7 +226,7 @@ SQL;
/**
* Calculates feed count per category.
- * @return array<array{'label':string,'data':int}>
+ * @return list<array{'label':string,'data':int}>
*/
public function calculateFeedByCategory(): array {
$sql = <<<SQL
@@ -239,14 +237,14 @@ WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
SQL;
- /** @var array<array{'label':string,'data':int}>|null @res */
+ /** @var list<array{'label':string,'data':int}>|null @res */
$res = $this->fetchAssoc($sql);
return $res == null ? [] : $res;
}
/**
* Calculates entry count per category.
- * @return array<array{'label':string,'data':int}>
+ * @return list<array{'label':string,'data':int}>
*/
public function calculateEntryByCategory(): array {
$sql = <<<SQL
@@ -259,13 +257,13 @@ GROUP BY label
ORDER BY data DESC
SQL;
$res = $this->fetchAssoc($sql);
- /** @var array<array{'label':string,'data':int}>|null $res */
+ /** @var list<array{'label':string,'data':int}>|null $res */
return $res == null ? [] : $res;
}
/**
* Calculates the 10 top feeds based on their number of entries
- * @return array<array{'id':int,'name':string,'category':string,'count':int}>
+ * @return list<array{'id':int,'name':string,'category':string,'count':int}>
*/
public function calculateTopFeed(): array {
$sql = <<<SQL
@@ -281,11 +279,8 @@ ORDER BY count DESC
LIMIT 10
SQL;
$res = $this->fetchAssoc($sql);
- /** @var array<array{'id':int,'name':string,'category':string,'count':int}>|null $res */
+ /** @var list<array{'id':int,'name':string,'category':string,'count':int}>|null $res */
if (is_array($res)) {
- foreach ($res as &$dao) {
- FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'count']);
- }
return $res;
}
return [];
@@ -293,7 +288,7 @@ SQL;
/**
* Calculates the last publication date for each feed
- * @return array<array{'id':int,'name':string,'last_date':int,'nb_articles':int}>
+ * @return list<array{'id':int,'name':string,'last_date':int,'nb_articles':int}>
*/
public function calculateFeedLastDate(): array {
$sql = <<<SQL
@@ -307,11 +302,8 @@ GROUP BY f.id
ORDER BY name
SQL;
$res = $this->fetchAssoc($sql);
- /** @var array<array{'id':int,'name':string,'last_date':int,'nb_articles':int}>|null $res */
+ /** @var list<array{'id':int,'name':string,'last_date':int,'nb_articles':int}>|null $res */
if (is_array($res)) {
- foreach ($res as &$dao) {
- FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'last_date', 'nb_articles']);
- }
return $res;
}
return [];
@@ -319,7 +311,7 @@ SQL;
/**
* Gets days ready for graphs
- * @return array<string>
+ * @return list<string>
*/
public function getDays(): array {
return $this->convertToTranslatedJson([
@@ -335,7 +327,7 @@ SQL;
/**
* Gets months ready for graphs
- * @return array<string>
+ * @return list<string>
*/
public function getMonths(): array {
return $this->convertToTranslatedJson([
@@ -356,8 +348,8 @@ SQL;
/**
* Translates array content
- * @param array<string> $data
- * @return array<string>
+ * @param list<string> $data
+ * @return list<string>
*/
private function convertToTranslatedJson(array $data = []): array {
$translated = array_map(static fn(string $a) => _t('gen.date.' . $a), $data);
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index e26a73a65..d66899e8f 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -117,7 +117,7 @@ SQL;
}
}
- /** @return Traversable<array{'id':int,'name':string,'attributes'?:array<string,mixed>}> */
+ /** @return Traversable<array{id:int,name:string,attributes?:array<string,mixed>}> */
public function selectAll(): Traversable {
$sql = 'SELECT id, name, attributes FROM `_tag`';
$stm = $this->pdo->query($sql);
@@ -126,12 +126,12 @@ SQL;
return;
}
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':int,'name':string,'attributes'?:array<string,mixed>} $row */
+ /** @var array{id:int,name:string,attributes?:array<string,mixed>} $row */
yield $row;
}
}
- /** @return Traversable<array{'id_tag':int,'id_entry':string}> */
+ /** @return Traversable<array{id_tag:int,id_entry:int|numeric-string}> */
public function selectEntryTag(): Traversable {
$sql = 'SELECT id_tag, id_entry FROM `_entrytag`';
$stm = $this->pdo->query($sql);
@@ -140,9 +140,8 @@ SQL;
return;
}
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- FreshRSS_DatabaseDAO::pdoInt($row, ['id_tag']);
- FreshRSS_DatabaseDAO::pdoString($row, ['id_entry']);
- yield $row;
+ /** @var array{id_tag:int,id_entry:int|numeric-string}> $row */
+ yield $row; // @phpstan-ignore generator.valueType
}
}
@@ -173,17 +172,17 @@ SQL;
public function searchById(int $id): ?FreshRSS_Tag {
$res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE id=:id', [':id' => $id]);
- /** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */
+ /** @var list<array{id:int,name:string,attributes?:string}>|null $res */
return $res === null ? null : (current(self::daoToTags($res)) ?: null);
}
public function searchByName(string $name): ?FreshRSS_Tag {
$res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE name=:name', [':name' => $name]);
- /** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */
+ /** @var list<array{id:int,name:string,attributes?:string}>|null $res */
return $res === null ? null : (current(self::daoToTags($res)) ?: null);
}
- /** @return array<int,FreshRSS_Tag>|false */
+ /** @return list<FreshRSS_Tag>|false */
public function listTags(bool $precounts = false): array|false {
if ($precounts) {
$sql = <<<'SQL'
@@ -291,16 +290,16 @@ SQL;
}
/**
- * @param array<array{id_tag:int,id_entry:string}> $addLabels Labels to insert as batch
+ * @param iterable<array{id_tag:int,id_entry:numeric-string|int}> $addLabels Labels to insert as batch
* @return int|false Number of new entries or false in case of error
*/
- public function tagEntries(array $addLabels): int|false {
+ public function tagEntries(iterable $addLabels): int|false {
$hasValues = false;
$sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `_entrytag`(id_tag, id_entry) VALUES ';
foreach ($addLabels as $addLabel) {
$id_tag = (int)($addLabel['id_tag'] ?? 0);
$id_entry = $addLabel['id_entry'] ?? '';
- if ($id_tag > 0 && ctype_digit($id_entry)) {
+ if ($id_tag > 0 && (is_int($id_entry) || ctype_digit($id_entry))) {
$sql .= "({$id_tag},{$id_entry}),";
$hasValues = true;
}
@@ -320,7 +319,7 @@ SQL;
}
/**
- * @return array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}>|false
+ * @return array<int,array{id:int,name:string,checked:bool}>|false
*/
public function getTagsForEntry(string $id_entry): array|false {
$sql = <<<'SQL'
@@ -347,8 +346,8 @@ SQL;
}
/**
- * @param array<FreshRSS_Entry|numeric-string|array<string,string>> $entries
- * @return array<array{'id_entry':string,'id_tag':int,'name':string}>|false
+ * @param list<FreshRSS_Entry|numeric-string> $entries
+ * @return list<array{id_entry:int|numeric-string,id_tag:int,name:string}>|false
*/
public function getTagsForEntries(array $entries): array|false {
$sql = <<<'SQL'
@@ -372,29 +371,16 @@ SQL;
return $values;
}
$sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1) . '?)';
- if (is_array($entries[0])) {
- /** @var array<array<string,string>> $entries */
- foreach ($entries as $entry) {
- if (!empty($entry['id'])) {
- $values[] = $entry['id'];
- }
- }
- } elseif (is_object($entries[0])) {
- /** @var array<FreshRSS_Entry> $entries */
- foreach ($entries as $entry) {
- $values[] = $entry->id();
- }
- } else {
- /** @var array<numeric-string> $entries */
- foreach ($entries as $entry) {
- $values[] = $entry;
- }
+ foreach ($entries as $entry) {
+ $values[] = is_object($entry) ? $entry->id() : $entry;
}
}
$stm = $this->pdo->prepare($sql);
if ($stm !== false && $stm->execute($values)) {
- return $stm->fetchAll(PDO::FETCH_ASSOC);
+ $result = $stm->fetchAll(PDO::FETCH_ASSOC);
+ /** @var list<array{id_entry:int|numeric-string,id_tag:int,name:string}> $result; */
+ return $result;
}
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
@@ -404,7 +390,7 @@ SQL;
/**
* Produces an array: for each entry ID (prefixed by `e_`), associate a list of labels.
* Used by API and by JSON export, to speed up queries (would be very expensive to perform a label look-up on each entry individually).
- * @param array<FreshRSS_Entry|numeric-string> $entries the list of entries for which to retrieve the labels.
+ * @param list<FreshRSS_Entry|numeric-string> $entries the list of entries for which to retrieve the labels.
* @return array<string,array<string>> An array of the shape `[e_id_entry => ["label 1", "label 2"]]`
*/
public function getEntryIdsTagNames(array $entries): array {
@@ -421,8 +407,8 @@ SQL;
}
/**
- * @param iterable<array{'id':int,'name':string,'attributes'?:string}> $listDAO
- * @return array<int,FreshRSS_Tag>
+ * @param iterable<array{id:int,name:string,attributes?:string}> $listDAO
+ * @return list<FreshRSS_Tag>
*/
private static function daoToTags(iterable $listDAO): array {
$list = [];
@@ -438,7 +424,7 @@ SQL;
if (isset($dao['unreads'])) {
$tag->_nbUnread($dao['unreads']);
}
- $list[$tag->id()] = $tag;
+ $list[] = $tag;
}
return $list;
}
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index 2a55a84db..cd66723bf 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -7,7 +7,7 @@ class FreshRSS_Themes extends Minz_Model {
private static string $defaultIconsUrl = '/themes/icons/';
public static string $defaultTheme = 'Origine';
- /** @return array<string> */
+ /** @return list<string> */
public static function getList(): array {
return array_values(array_diff(
scandir(PUBLIC_PATH . self::$themesUrl) ?: [],
@@ -15,7 +15,7 @@ class FreshRSS_Themes extends Minz_Model {
));
}
- /** @return array<string,array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}}> */
+ /** @return array<string,array{id:string,name:string,author:string,description:string,version:float|string,files:array<string>,theme-color?:string|array{dark?:string,light?:string,default?:string}}> */
public static function get(): array {
$themes_list = self::getList();
$list = [];
@@ -29,7 +29,7 @@ class FreshRSS_Themes extends Minz_Model {
}
/**
- * @return false|array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}}
+ * @return false|array{id:string,name:string,author:string,description:string,version:float|string,files:array<string>,theme-color?:string|array{dark?:string,light?:string,default?:string}}
*/
public static function get_infos(string $theme_id): array|false {
$theme_dir = PUBLIC_PATH . self::$themesUrl . $theme_id;
@@ -38,13 +38,24 @@ class FreshRSS_Themes extends Minz_Model {
if (file_exists($json_filename)) {
$content = file_get_contents($json_filename) ?: '';
$res = json_decode($content, true);
- if (is_array($res) &&
- !empty($res['name']) &&
- isset($res['files']) &&
- is_array($res['files'])) {
- $res['id'] = $theme_id;
- /** @var array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}} */
- return $res;
+ if (is_array($res)) {
+ $result = [
+ 'id' => $theme_id,
+ 'name' => is_string($res['name'] ?? null) ? $res['name'] : '',
+ 'author' => is_string($res['author'] ?? null) ? $res['author'] : '',
+ 'description' => is_string($res['description'] ?? null) ? $res['description'] : '',
+ 'version' => is_string($res['version'] ?? null) || is_numeric($res['version'] ?? null) ? $res['version'] : '0',
+ 'files' => is_array($res['files']) && is_array_values_string($res['files']) ? array_values($res['files']) : [],
+ 'theme-color' => is_string($res['theme-color'] ?? null) ? $res['theme-color'] : '',
+ ];
+ if (empty($result['theme-color']) && is_array($res['theme-color'])) {
+ $result['theme-color'] = [
+ 'dark' => is_string($res['theme-color']['dark'] ?? null) ? $res['theme-color']['dark'] : '',
+ 'light' => is_string($res['theme-color']['light'] ?? null) ? $res['theme-color']['light'] : '',
+ 'default' => is_string($res['theme-color']['default'] ?? null) ? $res['theme-color']['default'] : '',
+ ];
+ }
+ return $result;
}
}
}
@@ -56,7 +67,7 @@ class FreshRSS_Themes extends Minz_Model {
private static array $themeIcons;
/**
- * @return false|array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}}
+ * @return false|array{id:string,name:string,author:string,description:string,version:float|string,files:array<string>,theme-color?:string|array{dark?:string,light?:string,default?:string}}
*/
public static function load(string $theme_id): array|false {
$infos = self::get_infos($theme_id);
diff --git a/app/Models/UserConfiguration.php b/app/Models/UserConfiguration.php
index 8c2129744..4d465bf67 100644
--- a/app/Models/UserConfiguration.php
+++ b/app/Models/UserConfiguration.php
@@ -3,7 +3,7 @@ declare(strict_types=1);
/**
* @property string $apiPasswordHash
- * @property array{'keep_period':string|false,'keep_max':int|false,'keep_min':int|false,'keep_favourites':bool,'keep_labels':bool,'keep_unreads':bool} $archiving
+ * @property array{keep_period:string|false,keep_max:int|false,keep_min:int|false,keep_favourites:bool,keep_labels:bool,keep_unreads:bool} $archiving
* @property bool $auto_load_more
* @property bool $auto_remove_article
* @property bool $bottomline_date
@@ -42,7 +42,7 @@ declare(strict_types=1);
* @property bool $onread_jump_next
* @property string $passwordHash
* @property int $posts_per_page
- * @property array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string,'token'?:string}> $queries
+ * @property array<int,array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string}> $queries
* @property bool $reading_confirm
* @property int $since_hours_posts_per_rss
* @property bool $show_fav_unread
@@ -51,7 +51,7 @@ declare(strict_types=1);
* @property int $simplify_over_n_feeds
* @property bool $show_nav_buttons
* @property 'ASC'|'DESC' $sort_order
- * @property array<string,array<string>> $sharing
+ * @property array<string,array<string,string>> $sharing
* @property array<string,string> $shortcuts
* @property bool $sides_close_article
* @property bool $sticky_post
@@ -94,8 +94,9 @@ final class FreshRSS_UserConfiguration extends Minz_Configuration {
* @throws Minz_FileNotExistException
*/
public static function default(): FreshRSS_UserConfiguration {
+ /** @var FreshRSS_UserConfiguration|null $default_user_conf */
static $default_user_conf = null;
- if ($default_user_conf == null) {
+ if ($default_user_conf === null) {
$namespace = 'user_default';
FreshRSS_UserConfiguration::register($namespace, '_', FRESHRSS_PATH . '/config-user.default.php');
$default_user_conf = FreshRSS_UserConfiguration::get($namespace);
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index 5ae57dd65..4cbfa7412 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -8,6 +8,9 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
try {
$sql = $GLOBALS['SQL_CREATE_TABLES'];
+ if (!is_string($sql)) {
+ throw new Exception('SQL_CREATE_TABLES is not a string!');
+ }
$ok = $this->pdo->exec($sql) !== false; //Note: Only exec() can take multiple statements safely.
} catch (Exception $e) {
$ok = false;
@@ -29,7 +32,11 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
}
require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php');
- $ok = $this->pdo->exec($GLOBALS['SQL_DROP_TABLES']) !== false;
+ $sql = $GLOBALS['SQL_DROP_TABLES'];
+ if (!is_string($sql)) {
+ throw new Exception('SQL_DROP_TABLES is not a string!');
+ }
+ $ok = $this->pdo->exec($sql) !== false;
if ($ok) {
$this->close();
diff --git a/app/Models/View.php b/app/Models/View.php
index 4ce837922..aad512a39 100644
--- a/app/Models/View.php
+++ b/app/Models/View.php
@@ -10,7 +10,7 @@ class FreshRSS_View extends Minz_View {
public $callbackBeforeFeeds;
/** @var callable */
public $callbackBeforePagination;
- /** @var array<int,FreshRSS_Category> */
+ /** @var list<FreshRSS_Category> */
public array $categories;
public ?FreshRSS_Category $category = null;
public ?FreshRSS_Tag $tag = null;
@@ -19,12 +19,12 @@ class FreshRSS_View extends Minz_View {
public $entries;
public ?FreshRSS_Entry $entry = null;
public ?FreshRSS_Feed $feed = null;
- /** @var array<int,FreshRSS_Feed> */
+ /** @var list<FreshRSS_Feed> */
public array $feeds;
public int $nbUnreadTags;
- /** @var array<int,FreshRSS_Tag> */
+ /** @var list<FreshRSS_Tag> */
public array $tags;
- /** @var array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}> */
+ /** @var array<int,array{id:int,name:string,checked:bool}> */
public array $tagsForEntry;
/** @var array<string,array<string>> */
public array $tagsForEntries;
@@ -37,12 +37,12 @@ class FreshRSS_View extends Minz_View {
public bool $signalError;
// Manage users
- /** @var array{'feed_count':int,'article_count':int,'database_size':int,'language':string,'mail_login':string,'enabled':bool,'is_admin':bool,'last_user_activity':string,'is_default':bool} */
+ /** @var array{feed_count:int,article_count:int,database_size:int,language:string,mail_login:string,enabled:bool,is_admin:bool,last_user_activity:string,is_default:bool} */
public array $details;
public bool $disable_aside;
public bool $show_email_field;
public string $username;
- /** @var array<array{'language':string,'enabled':bool,'is_admin':bool,'enabled':bool,'article_count':int,'database_size':int,'last_user_activity':string,'mail_login':string,'feed_count':int,'is_default':bool}> */
+ /** @var array<array{language:string,enabled:bool,is_admin:bool,enabled:bool,article_count:int,database_size:int,last_user_activity:string,mail_login:string,feed_count:int,is_default:bool}> */
public array $users;
// Updates
@@ -62,7 +62,7 @@ class FreshRSS_View extends Minz_View {
public int $size_user;
// Display
- /** @var array<string,array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}}> */
+ /** @var array<string,array{id:string,name:string,author:string,description:string,version:float|string,files:array<string>,theme-color?:string|array{dark?:string,light?:string,default?:string}}> */
public array $themes;
// Shortcuts
@@ -118,10 +118,10 @@ class FreshRSS_View extends Minz_View {
public bool $selectorSuccess;
// Extensions
- /** @var array<array{'name':string,'author':string,'description':string,'version':string,'entrypoint':string,'type':'system'|'user','url':string,'method':string,'directory':string}> */
+ /** @var array<array{name:string,author:string,description:string,version:string,entrypoint:string,type:'system'|'user',url:string,method:string,directory:string}> */
public array $available_extensions;
public ?Minz_Extension $ext_details = null;
- /** @var array{'system':array<Minz_Extension>,'user':array<Minz_Extension>} */
+ /** @var array{system:array<Minz_Extension>,user:array<Minz_Extension>} */
public array $extension_list;
public ?Minz_Extension $extension = null;
/** @var array<string,string> */
diff --git a/app/Models/ViewJavascript.php b/app/Models/ViewJavascript.php
index 2b3c87537..26280627f 100644
--- a/app/Models/ViewJavascript.php
+++ b/app/Models/ViewJavascript.php
@@ -3,11 +3,11 @@ declare(strict_types=1);
final class FreshRSS_ViewJavascript extends FreshRSS_View {
- /** @var array<int,FreshRSS_Category> */
+ /** @var list<FreshRSS_Category> */
public array $categories;
- /** @var array<int,FreshRSS_Feed> */
+ /** @var list<FreshRSS_Feed> */
public array $feeds;
- /** @var array<int,FreshRSS_Tag> */
+ /** @var list<FreshRSS_Tag> */
public array $tags;
public string $nonce;
diff --git a/app/Models/ViewStats.php b/app/Models/ViewStats.php
index 3810312db..e8e0a37bc 100644
--- a/app/Models/ViewStats.php
+++ b/app/Models/ViewStats.php
@@ -3,10 +3,10 @@ declare(strict_types=1);
final class FreshRSS_ViewStats extends FreshRSS_View {
- /** @var array<int,FreshRSS_Category> */
+ /** @var list<FreshRSS_Category> */
public array $categories;
public ?FreshRSS_Feed $feed = null;
- /** @var array<int,FreshRSS_Feed> */
+ /** @var list<FreshRSS_Feed> */
public array $feeds;
public bool $displaySlider = false;
@@ -14,7 +14,7 @@ final class FreshRSS_ViewStats extends FreshRSS_View {
public float $averageDayOfWeek;
public float $averageHour;
public float $averageMonth;
- /** @var array<string> */
+ /** @var list<string> */
public array $days;
/** @var array<string,array<int,int|string>> */
public array $entryByCategory;
@@ -30,11 +30,11 @@ final class FreshRSS_ViewStats extends FreshRSS_View {
public array $last30DaysLabel;
/** @var array<int,string> */
public array $last30DaysLabels;
- /** @var array<string,string> */
+ /** @var list<string> */
public array $months;
- /** @var array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false */
+ /** @var array{total:int,count_unreads:int,count_reads:int,count_favorites:int}|false */
public $repartition;
- /** @var array{'main_stream':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false,'all_feeds':array{'total':int,'count_unreads':int,'count_reads':int,'count_favorites':int}|false} */
+ /** @var array{main_stream:array{total:int,count_unreads:int,count_reads:int,count_favorites:int}|false,all_feeds:array{total:int,count_unreads:int,count_reads:int,count_favorites:int}|false} */
public array $repartitions;
/** @var array<int,int> */
public array $repartitionDayOfWeek;
@@ -42,6 +42,6 @@ final class FreshRSS_ViewStats extends FreshRSS_View {
public array $repartitionHour;
/** @var array<int,int> */
public array $repartitionMonth;
- /** @var array<array{'id':int,'name':string,'category':string,'count':int}> */
+ /** @var list<array{id:int,name:string,category:string,count:int}> */
public array $topFeed;
}