aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/categoryController.php2
-rw-r--r--app/Controllers/configureController.php22
-rw-r--r--app/Controllers/entryController.php4
-rw-r--r--app/Controllers/extensionController.php15
-rwxr-xr-xapp/Controllers/feedController.php2
-rw-r--r--app/Controllers/importExportController.php90
-rw-r--r--app/Controllers/indexController.php8
-rw-r--r--app/Controllers/javascriptController.php5
-rw-r--r--app/Controllers/statsController.php1
-rw-r--r--app/Controllers/updateController.php8
-rw-r--r--app/Mailers/UserMailer.php1
-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
-rw-r--r--app/Utils/dotNotationUtil.php3
-rwxr-xr-xapp/actualize_script.php2
-rw-r--r--app/install.php69
-rw-r--r--app/views/entry/bookmark.phtml2
-rw-r--r--app/views/helpers/category/update.phtml2
-rw-r--r--app/views/helpers/export/opml.phtml6
-rw-r--r--app/views/helpers/feed/update.phtml2
-rw-r--r--app/views/helpers/logs_pagination.phtml2
-rw-r--r--app/views/index/global.phtml2
-rw-r--r--app/views/index/logs.phtml6
-rw-r--r--app/views/stats/index.phtml2
55 files changed, 557 insertions, 521 deletions
diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php
index 8b42e372a..9e27a5a4d 100644
--- a/app/Controllers/categoryController.php
+++ b/app/Controllers/categoryController.php
@@ -197,7 +197,6 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
}
// Remove related queries.
- /** @var array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string}> $queries */
$queries = remove_query_by_get('c_' . $id, FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save();
@@ -239,7 +238,6 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
// Remove related queries
foreach ($feeds as $feed) {
- /** @var array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string}> */
$queries = remove_query_by_get('f_' . $feed->id(), FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
}
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 55fd48393..5a60daa55 100644
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -176,10 +176,17 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
FreshRSS_View::appendScript(Minz_Url::display('/scripts/draggable.js?' . @filemtime(PUBLIC_PATH . '/scripts/draggable.js')));
if (Minz_Request::isPost()) {
- $params = $_POST;
- FreshRSS_Context::userConf()->sharing = $params['share'];
- FreshRSS_Context::userConf()->save();
- invalidateHttpCache();
+ $share = $_POST['share'] ?? null;
+ if (is_array($share)) {
+ $share = array_filter($share, fn($value, $key): bool =>
+ is_string($key) && is_array($value) &&
+ is_array_values_string($value),
+ ARRAY_FILTER_USE_BOTH);
+ /** @var array<string,array<string,string>> $share */
+ FreshRSS_Context::userConf()->sharing = $share;
+ FreshRSS_Context::userConf()->save();
+ invalidateHttpCache();
+ }
Minz_Request::good(_t('feedback.conf.updated'), [ 'c' => 'configure', 'a' => 'integration' ]);
}
@@ -308,7 +315,7 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
FreshRSS_View::appendScript(Minz_Url::display('/scripts/draggable.js?' . @filemtime(PUBLIC_PATH . '/scripts/draggable.js')));
if (Minz_Request::isPost()) {
- /** @var array<int,array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string,'token'?:string}> $params */
+ /** @var array<int,array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string}> $params */
$params = Minz_Request::paramArray('queries');
$queries = [];
@@ -390,7 +397,7 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
$queryParams['search'] = htmlspecialchars_decode($params['search'], ENT_QUOTES);
}
if (!empty($params['state']) && is_array($params['state'])) {
- $queryParams['state'] = (int)array_sum($params['state']);
+ $queryParams['state'] = (int)array_sum(array_map('intval', $params['state']));
}
if (empty($params['token']) || !is_string($params['token'])) {
$queryParams['token'] = FreshRSS_UserQuery::generateToken($name);
@@ -453,9 +460,10 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
foreach (FreshRSS_Context::userConf()->queries as $key => $query) {
$queries[$key] = (new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
}
- $params = $_GET;
+ $params = array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY);
unset($params['name']);
unset($params['rid']);
+ /** @var array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string,shareRss?:bool,shareOpml?:bool,description?:string,imageUrl?:string} $params */
$params['url'] = Minz_Url::display(['params' => $params]);
$params['name'] = _t('conf.query.number', count($queries) + 1);
$queries[] = (new FreshRSS_UserQuery($params, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php
index 5637bd101..8e5dbaa80 100644
--- a/app/Controllers/entryController.php
+++ b/app/Controllers/entryController.php
@@ -162,7 +162,7 @@ class FreshRSS_entry_Controller extends FreshRSS_ActionController {
}
}
} else {
- /** @var array<numeric-string> $idArray */
+ /** @var list<numeric-string> $idArray */
$idArray = Minz_Request::paramArrayString('id');
$idString = Minz_Request::paramString('id');
if (count($idArray) > 0) {
@@ -177,7 +177,7 @@ class FreshRSS_entry_Controller extends FreshRSS_ActionController {
$tagsForEntries = $tagDAO->getTagsForEntries($ids) ?: [];
$tags = [];
foreach ($tagsForEntries as $line) {
- $tags['t_' . $line['id_tag']][] = $line['id_entry'];
+ $tags['t_' . $line['id_tag']][] = (string)$line['id_entry'];
}
$this->view->tagsForEntries = $tags;
}
diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php
index 42538153d..efaee8534 100644
--- a/app/Controllers/extensionController.php
+++ b/app/Controllers/extensionController.php
@@ -39,8 +39,8 @@ class FreshRSS_extension_Controller extends FreshRSS_ActionController {
}
/**
- * fetch extension list from GitHub
- * @return array<array{'name':string,'author':string,'description':string,'version':string,'entrypoint':string,'type':'system'|'user','url':string,'method':string,'directory':string}>
+ * Fetch extension list from GitHub
+ * @return list<array{name:string,author:string,description:string,version:string,entrypoint:string,type:'system'|'user',url:string,method:string,directory:string}>
*/
protected function getAvailableExtensionList(): array {
$extensionListUrl = 'https://raw.githubusercontent.com/FreshRSS/Extensions/master/extensions.json';
@@ -76,17 +76,24 @@ class FreshRSS_extension_Controller extends FreshRSS_ActionController {
// the current implementation for now, unless it becomes too much effort maintain the extension list manually
$extensions = [];
foreach ($list['extensions'] as $extension) {
+ if (!is_array($extension)) {
+ continue;
+ }
if (isset($extension['version']) && is_numeric($extension['version'])) {
$extension['version'] = (string)$extension['version'];
}
- foreach (['author', 'description', 'directory', 'entrypoint', 'method', 'name', 'type', 'url', 'version'] as $key) {
- if (empty($extension[$key]) || !is_string($extension[$key])) {
+ $keys = ['author', 'description', 'directory', 'entrypoint', 'method', 'name', 'type', 'url', 'version'];
+ $extension = array_intersect_key($extension, array_flip($keys)); // Keep only valid keys
+ $extension = array_filter($extension, 'is_string');
+ foreach ($keys as $key) {
+ if (empty($extension[$key])) {
continue 2;
}
}
if (!in_array($extension['type'], ['system', 'user'], true)) {
continue;
}
+ /** @var array{name:string,author:string,description:string,version:string,entrypoint:string,type:'system'|'user',url:string,method:string,directory:string} $extension */
$extensions[] = $extension;
}
return $extensions;
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 60ee6d579..bf20f0747 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -799,7 +799,6 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
$entryDAO = FreshRSS_Factory::createEntryDao();
- /** @var array<array{id_tag:int,id_entry:string}> $applyLabels */
$applyLabels = [];
foreach (FreshRSS_Entry::fromTraversable($entryDAO->selectAll($nbNewEntries)) as $entry) {
foreach ($labels as $label) {
@@ -1003,7 +1002,6 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
// TODO: Delete old favicon
// Remove related queries
- /** @var array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string}> $queries */
$queries = remove_query_by_get('f_' . $feed_id, FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save();
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index afb1cbfec..1436ffc68 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -169,12 +169,13 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
Minz_Request::forward(['c' => 'importExport', 'a' => 'index'], true);
}
- $file = $_FILES['file'];
- $status_file = $file['error'];
+ $file = $_FILES['file'] ?? null;
+ $status_file = is_array($file) ? $file['error'] ?? -1 : -1;
- if ($status_file !== 0) {
- Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file);
+ if (!is_array($file) || $status_file !== 0 || !is_string($file['name'] ?? null) || !is_string($file['tmp_name'] ?? null)) {
+ Minz_Log::warning('File cannot be uploaded. Error code: ' . (is_numeric($status_file) ? $status_file : -1));
Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'), [ 'c' => 'importExport', 'a' => 'index' ]);
+ return;
}
if (function_exists('set_time_limit')) {
@@ -232,33 +233,36 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
private function ttrssXmlToJson(string $xml): string|false {
$table = (array)simplexml_load_string($xml, options: LIBXML_NOBLANKS | LIBXML_NOCDATA);
$table['items'] = $table['article'] ?? [];
+ if (!is_array($table['items'])) {
+ $table['items'] = [];
+ }
unset($table['article']);
for ($i = count($table['items']) - 1; $i >= 0; $i--) {
$item = (array)($table['items'][$i]);
$item = array_filter($item, static fn($v) =>
// Filter out empty properties, potentially reported as empty objects
(is_string($v) && trim($v) !== '') || !empty($v));
- $item['updated'] = isset($item['updated']) ? strtotime($item['updated']) : '';
+ $item['updated'] = is_string($item['updated'] ?? null) ? strtotime($item['updated']) : '';
$item['published'] = $item['updated'];
$item['content'] = ['content' => $item['content'] ?? ''];
- $item['categories'] = isset($item['tag_cache']) ? [$item['tag_cache']] : [];
+ $item['categories'] = is_string($item['tag_cache'] ?? null) ? [$item['tag_cache']] : [];
if (!empty($item['marked'])) {
$item['categories'][] = 'user/-/state/com.google/starred';
}
if (!empty($item['published'])) {
$item['categories'][] = 'user/-/state/com.google/broadcast';
}
- if (!empty($item['label_cache'])) {
+ if (is_string($item['label_cache'] ?? null)) {
$labels_cache = json_decode($item['label_cache'], true);
if (is_array($labels_cache)) {
foreach ($labels_cache as $label_cache) {
- if (!empty($label_cache[1]) && is_string($label_cache[1])) {
+ if (is_array($label_cache) && !empty($label_cache[1]) && is_string($label_cache[1])) {
$item['categories'][] = 'user/-/label/' . trim($label_cache[1]);
}
}
}
}
- $item['alternate'][0]['href'] = $item['link'] ?? '';
+ $item['alternate'] = [['href' => $item['link'] ?? '']];
$item['origin'] = [
'title' => $item['feed_title'] ?? '',
'feedUrl' => $item['feed_url'] ?? '',
@@ -290,6 +294,9 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
return false;
}
$items = $article_object['items'] ?? $article_object;
+ if (!is_array($items)) {
+ $items = [];
+ }
$mark_as_read = FreshRSS_Context::userConf()->mark_when['reception'] ? 1 : 0;
@@ -302,29 +309,32 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
// First, we check feeds of articles are in DB (and add them if needed).
foreach ($items as &$item) {
- if (!isset($item['guid']) && isset($item['id'])) {
+ if (!is_array($item)) {
+ continue;
+ }
+ if (!is_string($item['guid'] ?? null) && is_string($item['id'] ?? null)) {
$item['guid'] = $item['id'];
}
- if (empty($item['guid'])) {
+ if (!is_string($item['guid'] ?? null)) {
continue;
}
- if (empty($item['origin'])) {
+ if (!is_array($item['origin'] ?? null)) {
$item['origin'] = [];
}
- if (empty($item['origin']['title']) || trim($item['origin']['title']) === '') {
+ if (!is_string($item['origin']['title'] ?? null) || trim($item['origin']['title']) === '') {
$item['origin']['title'] = 'Import';
}
- if (!empty($item['origin']['feedUrl'])) {
+ if (is_string($item['origin']['feedUrl'] ?? null)) {
$feedUrl = $item['origin']['feedUrl'];
- } elseif (!empty($item['origin']['streamId']) && str_starts_with($item['origin']['streamId'], 'feed/')) {
+ } elseif (is_string($item['origin']['streamId'] ?? null) && str_starts_with($item['origin']['streamId'], 'feed/')) {
$feedUrl = substr($item['origin']['streamId'], 5); //Google Reader
$item['origin']['feedUrl'] = $feedUrl;
- } elseif (!empty($item['origin']['htmlUrl'])) {
+ } elseif (is_string($item['origin']['htmlUrl'] ?? null)) {
$feedUrl = $item['origin']['htmlUrl'];
} else {
$feedUrl = 'http://import.localhost/import.xml';
$item['origin']['feedUrl'] = $feedUrl;
- $item['origin']['disable'] = true;
+ $item['origin']['disable'] = 'true';
}
$feed = new FreshRSS_Feed($feedUrl);
$feed = $this->feedDAO->searchByUrl($feed->url());
@@ -335,7 +345,8 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
// Oops, no more place!
Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds']));
} else {
- $feed = $this->addFeedJson($item['origin']);
+ $origin = array_filter($item['origin'], fn($value, $key): bool => is_string($key) && is_string($value), ARRAY_FILTER_USE_BOTH);
+ $feed = $this->addFeedJson($origin);
}
if ($feed === null) {
@@ -375,19 +386,24 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
$newGuids = [];
$this->entryDAO->beginTransaction();
foreach ($items as &$item) {
- if (empty($item['guid']) || empty($article_to_feed[$item['guid']])) {
+ if (!is_array($item) || empty($item['guid']) || !is_string($item['guid']) || empty($article_to_feed[$item['guid']])) {
// Related feed does not exist for this entry, do nothing.
continue;
}
$feed_id = $article_to_feed[$item['guid']];
- $author = $item['author'] ?? '';
+ $author = is_string($item['author'] ?? null) ? $item['author'] : '';
$is_starred = null; // null is used to preserve the current state if that item exists and is already starred
$is_read = null;
- $tags = empty($item['categories']) ? [] : $item['categories'];
+ $tags = is_array($item['categories'] ?? null) ? $item['categories'] : [];
$labels = [];
for ($i = count($tags) - 1; $i >= 0; $i--) {
- $tag = trim($tags[$i]);
+ $tag = $tags[$i];
+ if (!is_string($tag)) {
+ unset($tags[$i]);
+ continue;
+ }
+ $tag = trim($tag);
if (preg_match('%^user/[A-Za-z0-9_-]+/%', $tag)) {
if (preg_match('%^user/[A-Za-z0-9_-]+/state/com.google/starred$%', $tag)) {
$is_starred = true;
@@ -401,6 +417,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
unset($tags[$i]);
}
}
+ $tags = array_values(array_filter($tags, 'is_string'));
if ($starred && !$is_starred) {
//If the article has no label, mark it as starred (old format)
$is_starred = empty($labels);
@@ -409,41 +426,38 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
$is_read = $mark_as_read;
}
- if (isset($item['alternate'][0]['href'])) {
+ if (is_array($item['alternate']) && is_array($item['alternate'][0] ?? null) && is_string($item['alternate'][0]['href'] ?? null)) {
$url = $item['alternate'][0]['href'];
- } elseif (isset($item['url'])) {
+ } elseif (is_string($item['url'] ?? null)) {
$url = $item['url']; //FeedBin
} else {
$url = '';
}
- if (!is_string($url)) {
- $url = '';
- }
- $title = empty($item['title']) ? $url : $item['title'];
+ $title = is_string($item['title'] ?? null) ? $item['title'] : $url;
- if (isset($item['content']['content']) && is_string($item['content']['content'])) {
+ if (is_array($item['content'] ?? null) && is_string($item['content']['content'] ?? null)) {
$content = $item['content']['content'];
- } elseif (isset($item['summary']['content']) && is_string($item['summary']['content'])) {
+ } elseif (is_array($item['summary']) && is_string($item['summary']['content'] ?? null)) {
$content = $item['summary']['content'];
- } elseif (isset($item['content']) && is_string($item['content'])) {
+ } elseif (is_string($item['content'] ?? null)) {
$content = $item['content']; //FeedBin
} else {
$content = '';
}
$content = sanitizeHTML($content, $url);
- if (!empty($item['published'])) {
- $published = '' . $item['published'];
- } elseif (!empty($item['timestampUsec'])) {
- $published = substr('' . $item['timestampUsec'], 0, -6);
- } elseif (!empty($item['updated'])) {
- $published = '' . $item['updated'];
+ if (is_int($item['published'] ?? null) || is_string($item['published'] ?? null)) {
+ $published = (string)$item['published'];
+ } elseif (is_int($item['timestampUsec'] ?? null) || is_string($item['timestampUsec'] ?? null)) {
+ $published = substr((string)$item['timestampUsec'], 0, -6);
+ } elseif (is_int($item['updated'] ?? null) || is_string($item['updated'] ?? null)) {
+ $published = (string)$item['updated'];
} else {
$published = '0';
}
if (!ctype_digit($published)) {
- $published = '' . strtotime($published);
+ $published = (string)strtotime($published);
}
if (strlen($published) > 10) { // Milliseconds, e.g. Feedly
$published = substr($published, 0, -3);
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 70bb25a77..fffc15b2a 100644
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -170,8 +170,10 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
$this->view->html_url = Minz_Url::display('', 'html', true);
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title();
+
+ $queryString = $_SERVER['QUERY_STRING'] ?? '';
$this->view->rss_url = htmlspecialchars(
- PUBLIC_TO_INDEX_PATH . '/' . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']), ENT_COMPAT, 'UTF-8');
+ PUBLIC_TO_INDEX_PATH . '/' . ($queryString === '' || !is_string($queryString) ? '' : '?' . $queryString), ENT_COMPAT, 'UTF-8');
// No layout for RSS output.
$this->view->_layout(null);
@@ -216,7 +218,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
Minz_Error::error(404);
return;
}
- $this->view->categories = [ $cat->id() => $cat ];
+ $this->view->categories = [ $cat ];
break;
case 'f':
// We most likely already have the feed object in cache
@@ -229,7 +231,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
return;
}
}
- $this->view->feeds = [ $feed->id() => $feed ];
+ $this->view->feeds = [ $feed ];
break;
case 's':
case 't':
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index 0cbcd0bd0..f7002cba8 100644
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -5,6 +5,7 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
/**
* @var FreshRSS_ViewJavascript
+ * @phpstan-ignore property.phpDocType
*/
protected $view;
@@ -53,6 +54,10 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
header('Pragma: no-cache');
$user = $_GET['user'] ?? '';
+ if (!is_string($user) || $user === '') {
+ Minz_Error::error(400);
+ return;
+ }
FreshRSS_Context::initUser($user);
if (FreshRSS_Context::hasUserConf()) {
try {
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index 062603930..ee3df4ea5 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -8,6 +8,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
/**
* @var FreshRSS_ViewStats
+ * @phpstan-ignore property.phpDocType
*/
protected $view;
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index c7623d0a4..f6ed00986 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -287,8 +287,8 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
Minz_Log::notice(_t('feedback.update.finished'));
Minz_Request::good(_t('feedback.update.finished'));
} else {
- Minz_Log::error(_t('feedback.update.error', $res));
- Minz_Request::bad(_t('feedback.update.error', $res), [ 'c' => 'update', 'a' => 'index' ]);
+ Minz_Log::error(_t('feedback.update.error', is_string($res) ? $res : 'unknown'));
+ Minz_Request::bad(_t('feedback.update.error', is_string($res) ? $res : 'unknown'), [ 'c' => 'update', 'a' => 'index' ]);
}
} else {
$res = false;
@@ -321,8 +321,8 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
'params' => ['post_conf' => '1'],
], true);
} else {
- Minz_Log::error(_t('feedback.update.error', $res));
- Minz_Request::bad(_t('feedback.update.error', $res), [ 'c' => 'update', 'a' => 'index' ]);
+ Minz_Log::error(_t('feedback.update.error', is_string($res) ? $res : 'unknown'));
+ Minz_Request::bad(_t('feedback.update.error', is_string($res) ? $res : 'unknown'), [ 'c' => 'update', 'a' => 'index' ]);
}
}
}
diff --git a/app/Mailers/UserMailer.php b/app/Mailers/UserMailer.php
index 4d657bf69..e319cfbb4 100644
--- a/app/Mailers/UserMailer.php
+++ b/app/Mailers/UserMailer.php
@@ -8,6 +8,7 @@ class FreshRSS_User_Mailer extends Minz_Mailer {
/**
* @var FreshRSS_View
+ * @phpstan-ignore property.phpDocType
*/
protected $view;
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;
}
diff --git a/app/Utils/dotNotationUtil.php b/app/Utils/dotNotationUtil.php
index 620ed7db1..77ae96c30 100644
--- a/app/Utils/dotNotationUtil.php
+++ b/app/Utils/dotNotationUtil.php
@@ -65,6 +65,7 @@ final class FreshRSS_dotNotation_Util
* Determine if the given key exists in the provided array.
*
* @param \ArrayAccess<string,mixed>|array<string,mixed>|mixed $array
+ * @phpstan-assert-if-true \ArrayAccess<string,mixed>|array<string,mixed> $array
*/
private static function exists($array, string $key): bool {
if ($array instanceof \ArrayAccess) {
@@ -85,7 +86,7 @@ final class FreshRSS_dotNotation_Util
* mapping fields from the JSON object into RSS equivalents
* according to the dot-separated paths
*
- * @param array<string> $jf json feed
+ * @param array<int|string,mixed> $jf json feed
* @param string $feedSourceUrl the source URL for the feed
* @param array<string,string> $dotNotation dot notation to map JSON into RSS
* @param string $defaultRssTitle Default title of the RSS feed, if not already provided in dotNotation `feedTitle`
diff --git a/app/actualize_script.php b/app/actualize_script.php
index e55c1e080..d0ca72271 100755
--- a/app/actualize_script.php
+++ b/app/actualize_script.php
@@ -1,6 +1,6 @@
#!/usr/bin/env php
<?php
-// declare(strict_types=1); // Need to wait for PHP 8+ due to https://php.net/ob-implicit-flush
+declare(strict_types=1);
require(__DIR__ . '/../cli/_cli.php');
session_cache_limiter('');
diff --git a/app/install.php b/app/install.php
index a7b4ef09c..232d24c7c 100644
--- a/app/install.php
+++ b/app/install.php
@@ -10,7 +10,7 @@ require(LIB_PATH . '/lib_install.php');
Minz_Session::init('FreshRSS');
-if (isset($_GET['step'])) {
+if (isset($_GET['step']) && is_numeric($_GET['step'])) {
define('STEP', (int)$_GET['step']);
} else {
define('STEP', 0);
@@ -41,7 +41,7 @@ function initTranslate(): void {
}
function get_best_language(): string {
- $accept = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+ $accept = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) || !is_string($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
return strtolower(substr($accept, 0, 2));
}
@@ -102,19 +102,22 @@ function saveStep2(): void {
'bd_prefix' => false,
]);
} else {
- if (empty($_POST['type']) ||
- empty($_POST['host']) ||
- empty($_POST['user']) ||
- empty($_POST['base'])) {
+ if (empty($_POST['type']) || !is_string($_POST['type']) ||
+ empty($_POST['host']) || !is_string($_POST['host']) ||
+ empty($_POST['user']) || !is_string($_POST['user']) ||
+ empty($_POST['base']) || !is_string($_POST['base']) ||
+ !is_string($_POST['pass'] ?? null) || !is_string($_POST['prefix'] ?? null)
+ ) {
Minz_Session::_param('bd_error', 'Missing parameters!');
- }
- Minz_Session::_params([
+ } else {
+ Minz_Session::_params([
'bd_base' => substr($_POST['base'], 0, 64),
'bd_host' => $_POST['host'],
'bd_user' => $_POST['user'],
'bd_password' => $_POST['pass'],
'bd_prefix' => substr($_POST['prefix'], 0, 16),
]);
+ }
}
// We use dirname to remove the /i part
@@ -143,6 +146,9 @@ function saveStep2(): void {
$customConfig = include($customConfigPath);
if (is_array($customConfig)) {
$config_array = array_merge($customConfig, $config_array);
+ if (!is_string($config_array['default_user'] ?? null)) {
+ $config_array['default_user'] = '_';
+ }
}
}
@@ -157,6 +163,9 @@ function saveStep2(): void {
$ok = false;
try {
+ if (!is_string($config_array['default_user'])) {
+ throw new Exception('Invalid default user name');
+ }
Minz_User::change($config_array['default_user']);
$error = initDb();
Minz_User::change();
@@ -327,11 +336,11 @@ function checkStep3(): array {
$form = Minz_Session::paramString('auth_type') != '';
- $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user'];
- if ($defaultUser === null) {
+ $defaultUser = is_string($_POST['default_user'] ?? null) ? trim($_POST['default_user']) : '';
+ if ($defaultUser === '') {
$defaultUser = Minz_Session::paramString('default_user') == '' ? '' : Minz_Session::paramString('default_user');
}
- $data = is_writable(join_path(USERS_PATH, $defaultUser, 'config.php'));
+ $data = is_writable(USERS_PATH . '/' . $defaultUser . '/config.php');
return [
'conf' => $conf ? 'ok' : 'ko',
@@ -445,16 +454,15 @@ function getProcessUsername(): string {
/* check system environment */
function printStep1(): void {
$res = checkRequirements();
- $processUsername = getProcessUsername();
?>
<h2><?= _t('admin.check_install.php') ?></h2>
<noscript><p class="alert alert-warn"><span class="alert-head"><?= _t('gen.short.attention') ?></span> <?= _t('install.javascript_is_better') ?></p></noscript>
-
<?php
- $version = function_exists('curl_version') ? curl_version() : [];
printStep1Template('php', $res['php'], [PHP_VERSION, FRESHRSS_MIN_PHP_VERSION]);
printStep1Template('pdo', $res['pdo']);
- printStep1Template('curl', $res['curl'], [$version['version'] ?? '']);
+ $curlVersion = function_exists('curl_version') ? curl_version() : [];
+ $curlVersion = is_string($curlVersion['version'] ?? null) ? $curlVersion['version'] : '';
+ printStep1Template('curl', $res['curl'], [$curlVersion]);
printStep1Template('json', $res['json']);
printStep1Template('pcre', $res['pcre']);
printStep1Template('ctype', $res['ctype']);
@@ -465,6 +473,7 @@ function printStep1(): void {
?>
<h2><?= _t('admin.check_install.files') ?></h2>
<?php
+ $processUsername = getProcessUsername();
printStep1Template('data', $res['data'], [DATA_PATH, $processUsername]);
printStep1Template('cache', $res['cache'], [CACHE_PATH, $processUsername]);
printStep1Template('tmp', $res['tmp'], [TMP_PATH, $processUsername]);
@@ -516,7 +525,7 @@ function printStep2(): void {
<p class="alert alert-success"><span class="alert-head"><?= _t('gen.short.ok') ?></span> <?= _t('install.bdd.conf.ok') ?></p>
<?php } elseif ($s2['conn'] == 'ko') { ?>
<p class="alert alert-error"><span class="alert-head"><?= _t('gen.short.damn') ?></span> <?= _t('install.bdd.conf.ko'),
- (empty($_SESSION['bd_error']) ? '' : ' : ' . $_SESSION['bd_error']) ?></p>
+ (empty($_SESSION['bd_error']) || !is_string($_SESSION['bd_error']) ? '' : ' : ' . $_SESSION['bd_error']) ?></p>
<?php } ?>
<h2><?= _t('install.bdd.conf') ?></h2>
@@ -527,19 +536,19 @@ function printStep2(): void {
<select name="type" id="type" tabindex="1">
<?php if (extension_loaded('pdo_sqlite')) {?>
<option value="sqlite"
- <?= isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'sqlite' ? 'selected="selected"' : '' ?>>
+ <?= ($_SESSION['bd_type'] ?? null) === 'sqlite' ? 'selected="selected"' : '' ?>>
SQLite
</option>
<?php }?>
<?php if (extension_loaded('pdo_mysql')) {?>
<option value="mysql"
- <?= isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql' ? 'selected="selected"' : '' ?>>
+ <?= ($_SESSION['bd_type'] ?? null) === 'mysql' ? 'selected="selected"' : '' ?>>
MySQL / MariaDB
</option>
<?php }?>
<?php if (extension_loaded('pdo_pgsql')) {?>
<option value="pgsql"
- <?= isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'pgsql' ? 'selected="selected"' : '' ?>>
+ <?= ($_SESSION['bd_type'] ?? null) === 'pgsql' ? 'selected="selected"' : '' ?>>
PostgreSQL
</option>
<?php }?>
@@ -548,11 +557,18 @@ function printStep2(): void {
</div>
<div id="mysql">
+ <?php
+ $bd_base = is_string($_SESSION['bd_base'] ?? null) ? $_SESSION['bd_base'] : null;
+ $bd_host = is_string($_SESSION['bd_host'] ?? null) ? $_SESSION['bd_host'] : null;
+ $bd_password = is_string($_SESSION['bd_password'] ?? null) ? $_SESSION['bd_password'] : null;
+ $bd_prefix = is_string($_SESSION['bd_prefix'] ?? null) ? $_SESSION['bd_prefix'] : null;
+ $bd_user = is_string($_SESSION['bd_user'] ?? null) ? $_SESSION['bd_user'] : null;
+ ?>
<div class="form-group">
<label class="group-name" for="host"><?= _t('install.bdd.host') ?></label>
<div class="group-controls">
<input type="text" id="host" name="host" pattern="[0-9A-Z\/a-z_.\-]{1,64}(:[0-9]{2,5})?" value="<?=
- $_SESSION['bd_host'] ?? $system_default_config->db['host'] ?? '' ?>" tabindex="2" />
+ $bd_host ?? $system_default_config->db['host'] ?? '' ?>" tabindex="2" />
</div>
</div>
@@ -560,7 +576,7 @@ function printStep2(): void {
<label class="group-name" for="user"><?= _t('install.bdd.username') ?></label>
<div class="group-controls">
<input type="text" id="user" name="user" maxlength="64" pattern="[0-9A-Za-z@_.\-]{1,64}" value="<?=
- $_SESSION['bd_user'] ?? '' ?>" tabindex="3" />
+ $bd_user ?? '' ?>" tabindex="3" />
</div>
</div>
@@ -569,7 +585,7 @@ function printStep2(): void {
<div class="group-controls">
<div class="stick">
<input type="password" id="pass" name="pass" value="<?=
- $_SESSION['bd_password'] ?? '' ?>" tabindex="4" autocomplete="off" />
+ $bd_password ?? '' ?>" tabindex="4" autocomplete="off" />
<a class="btn toggle-password" data-toggle="pass" tabindex="5"><?= FreshRSS_Themes::icon('key') ?></a>
</div>
</div>
@@ -579,7 +595,7 @@ function printStep2(): void {
<label class="group-name" for="base"><?= _t('install.bdd') ?></label>
<div class="group-controls">
<input type="text" id="base" name="base" maxlength="64" pattern="[0-9A-Za-z_\-]{1,64}" value="<?=
- $_SESSION['bd_base'] ?? '' ?>" tabindex="6" />
+ $bd_base ?? '' ?>" tabindex="6" />
</div>
</div>
@@ -587,7 +603,7 @@ function printStep2(): void {
<label class="group-name" for="prefix"><?= _t('install.bdd.prefix') ?></label>
<div class="group-controls">
<input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?=
- $_SESSION['bd_prefix'] ?? $system_default_config->db['prefix'] ?? '' ?>" tabindex="7" />
+ $bd_prefix ?? $system_default_config->db['prefix'] ?? '' ?>" tabindex="7" />
</div>
</div>
</div>
@@ -611,7 +627,8 @@ function no_auth(string $auth_type): bool {
/* Create default user */
function printStep3(): void {
- $auth_type = $_SESSION['auth_type'] ?? '';
+ $auth_type = is_string($_SESSION['auth_type'] ?? null) ? $_SESSION['auth_type'] : '';
+ $default_user = is_string($_SESSION['default_user'] ?? null) ? $_SESSION['default_user'] : '';
$s3 = checkStep3();
if ($s3['all'] == 'ok') { ?>
<p class="alert alert-success"><span class="alert-head"><?= _t('gen.short.ok') ?></span> <?= _t('install.conf.ok') ?></p>
@@ -625,7 +642,7 @@ function printStep3(): void {
<label class="group-name" for="default_user"><?= _t('install.default_user') ?></label>
<div class="group-controls">
<input type="text" id="default_user" name="default_user" autocomplete="username" required="required" size="16"
- pattern="<?= FreshRSS_user_Controller::USERNAME_PATTERN ?>" value="<?= $_SESSION['default_user'] ?? '' ?>"
+ pattern="<?= FreshRSS_user_Controller::USERNAME_PATTERN ?>" value="<?= $default_user ?>"
placeholder="<?= httpAuthUser(false) == '' ? 'alice' : httpAuthUser(false) ?>" tabindex="1" />
<p class="help"><?= _i('help') ?> <?= _t('install.default_user.max_char') ?></p>
</div>
diff --git a/app/views/entry/bookmark.phtml b/app/views/entry/bookmark.phtml
index 81647352c..a124df673 100644
--- a/app/views/entry/bookmark.phtml
+++ b/app/views/entry/bookmark.phtml
@@ -7,7 +7,7 @@ header('Content-Type: application/json; charset=UTF-8');
$url = [
'c' => Minz_Request::controllerName(),
'a' => Minz_Request::actionName(),
- 'params' => $_GET,
+ 'params' => array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY),
];
$url['params']['is_favorite'] = (Minz_Request::paramTernary('is_favorite') ?? true) ? '0' : '1';
diff --git a/app/views/helpers/category/update.phtml b/app/views/helpers/category/update.phtml
index 8d97a6eec..5b81b6737 100644
--- a/app/views/helpers/category/update.phtml
+++ b/app/views/helpers/category/update.phtml
@@ -116,7 +116,7 @@
<legend><?= _t('sub.category.archiving') ?></legend>
<?php
$archiving = $this->category->attributeArray('archiving');
- /** @var array<'default'?:bool,'keep_period'?:string,'keep_max'?:int,'keep_min'?:int,'keep_favourites'?:bool,'keep_labels'?:bool,'keep_unreads'?:bool>|null $archiving */
+ /** @var array{default?:bool,keep_period?:string,keep_max?:int,keep_min?:int,keep_favourites?:bool,keep_labels?:bool,keep_unreads?:bool}|null $archiving */
if (empty($archiving)) {
$archiving = [ 'default' => true ];
} else {
diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml
index c37d8c7c4..37e728470 100644
--- a/app/views/helpers/export/opml.phtml
+++ b/app/views/helpers/export/opml.phtml
@@ -3,7 +3,7 @@ declare(strict_types=1);
/**
* @param array<FreshRSS_Feed> $feeds
- * @return array<array<string,string|bool|int>>
+ * @return list<array<string,string|bool|int>>
*/
function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
$outlines = [];
@@ -112,7 +112,9 @@ function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
if (!empty($curl_params[CURLOPT_HTTPHEADER]) && is_array($curl_params[CURLOPT_HTTPHEADER])) {
$headers = '';
foreach ($curl_params[CURLOPT_HTTPHEADER] as $header) {
- $headers .= $header . "\n";
+ if (is_string($header)) {
+ $headers .= $header . "\n";
+ }
}
$headers = trim($headers);
$outline['frss:CURLOPT_HTTPHEADER'] = $headers;
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index 6275d5486..7dd71a0da 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -305,7 +305,7 @@
</div>
<?php
$archiving = $this->feed->attributeArray('archiving');
- /** @var array<'default'?:bool,'keep_period'?:string,'keep_max'?:int,'keep_min'?:int,'keep_favourites'?:bool,'keep_labels'?:bool,'keep_unreads'?:bool>|null $archiving */
+ /** @var array{default?:bool,keep_period?:string,keep_max?:int,keep_min?:int,keep_favourites?:bool,keep_labels?:bool,keep_unreads?:bool}|null $archiving */
if (empty($archiving)) {
$archiving = [ 'default' => true ];
} else {
diff --git a/app/views/helpers/logs_pagination.phtml b/app/views/helpers/logs_pagination.phtml
index 77e3f3c82..b3c56253b 100644
--- a/app/views/helpers/logs_pagination.phtml
+++ b/app/views/helpers/logs_pagination.phtml
@@ -3,7 +3,7 @@
/** @var FreshRSS_View $this */
$c = Minz_Request::controllerName();
$a = Minz_Request::actionName();
- $params = $_GET;
+ $params = array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY);
?>
<?php if ($this->nbPage > 1) { ?>
<nav class="nav-pagination nav-list">
diff --git a/app/views/index/global.phtml b/app/views/index/global.phtml
index 72916f1a0..527c0b9c4 100644
--- a/app/views/index/global.phtml
+++ b/app/views/index/global.phtml
@@ -39,7 +39,7 @@
<main id="stream" class="global<?= $class ?>">
<h1 class="title_hidden"><?= _t('conf.reading.view.global') ?></h1>
<?php
- $params = $_GET;
+ $params = array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY);
unset($params['c']);
unset($params['a']);
$url_base = [
diff --git a/app/views/index/logs.phtml b/app/views/index/logs.phtml
index ec8bf3881..60b593344 100644
--- a/app/views/index/logs.phtml
+++ b/app/views/index/logs.phtml
@@ -11,14 +11,14 @@
<h1><?= _t('index.log') ?></h1>
<?php
- /** @var array<FreshRSS_Log> $items */
+ /** @var list<FreshRSS_Log> $items */
$items = $this->logsPaginator->items();
?>
<?php if (!empty($items)) { ?>
<form method="post" action="<?= _url('index', 'logs') ?>">
<?php $this->logsPaginator->render('logs_pagination.phtml', 'page'); ?>
-
+
<div id="loglist-wrapper" class="table-wrapper scrollbar-thin">
<table id="loglist">
<thead>
@@ -46,7 +46,7 @@
</table>
</div>
<?php $this->logsPaginator->render('logs_pagination.phtml', 'page'); ?>
-
+
<div class="form-group form-actions">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<input type="hidden" name="clearLogs" />
diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml
index 3a5db2fb8..31e6e51f9 100644
--- a/app/views/stats/index.phtml
+++ b/app/views/stats/index.phtml
@@ -102,7 +102,7 @@
* Generate a color palette.
*
* @param int $count The number of colors to generate.
- * @return array<int, string> An array of HSL color strings.
+ * @return array<int,string> An array of HSL color strings.
*/
function generateColorPalette(int $count): array {
$colors = [];