aboutsummaryrefslogtreecommitdiff
path: root/app/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controllers')
-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
10 files changed, 95 insertions, 62 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' ]);
}
}
}