diff options
Diffstat (limited to 'app/Controllers')
| -rw-r--r-- | app/Controllers/categoryController.php | 2 | ||||
| -rw-r--r-- | app/Controllers/configureController.php | 22 | ||||
| -rw-r--r-- | app/Controllers/entryController.php | 4 | ||||
| -rw-r--r-- | app/Controllers/extensionController.php | 15 | ||||
| -rwxr-xr-x | app/Controllers/feedController.php | 2 | ||||
| -rw-r--r-- | app/Controllers/importExportController.php | 90 | ||||
| -rw-r--r-- | app/Controllers/indexController.php | 8 | ||||
| -rw-r--r-- | app/Controllers/javascriptController.php | 5 | ||||
| -rw-r--r-- | app/Controllers/statsController.php | 1 | ||||
| -rw-r--r-- | app/Controllers/updateController.php | 8 |
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' ]); } } } |
