Kaynağa Gözat

Phpstan Level6 for View.php (#5269)

* Remarque's from Alkarex

* indentation

* indentation

* Apply suggestions from code review

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Remarque's from Alkarex

* A few improvements

* Remarque's from Alkarex

* Remarque's from Alkarex

* Remarque's from Alkarex

* Remarque's from Alkarex

* Fixes and improvments

* Fix getTagsForEntry

---------

Co-authored-by: Luc <sanchezluc+freshrss@gmail.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Luc SANCHEZ 3 yıl önce
ebeveyn
işleme
d23d10bcde

+ 1 - 1
app/Controllers/authController.php

@@ -111,7 +111,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
 		FreshRSS_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')));
 
 		$limits = FreshRSS_Context::$system_conf->limits;
-		$this->view->cookie_days = round($limits['cookie_duration'] / 86400, 1);
+		$this->view->cookie_days = (int)round($limits['cookie_duration'] / 86400, 1);
 
 		$isPOST = Minz_Request::isPost() && !Minz_Session::param('POST_to_GET');
 		Minz_Session::_param('POST_to_GET');

+ 3 - 3
app/Controllers/entryController.php

@@ -59,8 +59,8 @@ class FreshRSS_entry_Controller extends FreshRSS_ActionController {
 			FreshRSS_Context::$state = 0;
 		}
 
-		$params = array();
-		$this->view->tags = array();
+		$params = [];
+		$this->view->tagsForEntries = [];
 
 		$entryDAO = FreshRSS_Factory::createEntryDao();
 		if ($id == false) {
@@ -112,7 +112,7 @@ class FreshRSS_entry_Controller extends FreshRSS_ActionController {
 			foreach ($tagsForEntries as $line) {
 				$tags['t_' . $line['id_tag']][] = $line['id_entry'];
 			}
-			$this->view->tags = $tags;
+			$this->view->tagsForEntries = $tags;
 		}
 
 		if (!$this->ajax) {

+ 1 - 1
app/Controllers/subscriptionController.php

@@ -48,7 +48,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 		FreshRSS_View::appendScript(Minz_Url::display('/scripts/feed.js?' . @filemtime(PUBLIC_PATH . '/scripts/feed.js')));
 		FreshRSS_View::prependTitle(_t('sub.title') . ' · ');
 
-		$this->view->onlyFeedsWithError = Minz_Request::paramTernary('error');
+		$this->view->onlyFeedsWithError = Minz_Request::paramBoolean('error');
 
 		$id = Minz_Request::paramInt('id');
 		$this->view->displaySlider = false;

+ 2 - 2
app/Controllers/tagController.php

@@ -85,9 +85,9 @@ class FreshRSS_tag_Controller extends FreshRSS_ActionController {
 		$this->view->_layout(false);
 		header('Content-Type: application/json; charset=UTF-8');
 		header('Cache-Control: private, no-cache, no-store, must-revalidate');
-		$id_entry = Minz_Request::paramInt('id_entry');
+		$id_entry = Minz_Request::paramString('id_entry');
 		$tagDAO = FreshRSS_Factory::createTagDao();
-		$this->view->tags = $tagDAO->getTagsForEntry($id_entry);
+		$this->view->tagsForEntry = $tagDAO->getTagsForEntry($id_entry);
 	}
 
 	public function addAction(): void {

+ 2 - 1
app/Controllers/userController.php

@@ -629,7 +629,8 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 		$this->view->details = $this->retrieveUserDetails($username);
 	}
 
-	private function retrieveUserDetails($username) {
+	/** @return array<string,int|string|bool> */
+	private function retrieveUserDetails($username): array {
 		$feedDAO = FreshRSS_Factory::createFeedDao($username);
 		$entryDAO = FreshRSS_Factory::createEntryDao($username);
 		$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);

+ 8 - 8
app/Models/CategoryDAO.php

@@ -281,7 +281,8 @@ SQL;
 		return $categories;
 	}
 
-	public function listCategories($prePopulateFeeds = true, $details = false) {
+	/** @return array<FreshRSS_Category>|false */
+	public function listCategories(bool $prePopulateFeeds = true, bool $details = false) {
 		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, '
 				. ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads`, f.ttl ')
@@ -435,13 +436,12 @@ SQL;
 		return $n;
 	}
 
-	public static function daoToCategoryPrepopulated($listDAO) {
+	/**
+	 * @param array<string,mixed> $listDAO
+	 * @return array<int,FreshRSS_Category>
+	 */
+	private static function daoToCategoryPrepopulated(array $listDAO) {
 		$list = array();
-
-		if (!is_array($listDAO)) {
-			$listDAO = array($listDAO);
-		}
-
 		$previousLine = null;
 		$feedsDao = array();
 		$feedDao = FreshRSS_Factory::createFeedDAO();
@@ -481,7 +481,7 @@ SQL;
 		return $list;
 	}
 
-	public static function daoToCategory($listDAO) {
+	private static function daoToCategory($listDAO) {
 		$list = array();
 
 		if (!is_array($listDAO)) {

+ 4 - 2
app/Models/Entry.php

@@ -48,7 +48,8 @@ class FreshRSS_Entry extends Minz_Model {
 	 */
 	private $feed;
 
-	private $tags;
+	/** @var array<string> */
+	private $tags = [];
 	private $attributes = [];
 
 	public function __construct(int $feedId = 0, string $guid = '', string $title = '', string $authors = '', string $content = '',
@@ -347,7 +348,8 @@ HTML;
 		return $this->feedId;
 	}
 
-	public function tags($asString = false) {
+	/** @return string|array<string> */
+	public function tags(bool $asString = false) {
 		if ($asString) {
 			return $this->tags == null ? '' : '#' . implode(' #', $this->tags);
 		} else {

+ 2 - 3
app/Models/EntryDAO.php

@@ -1214,9 +1214,8 @@ SQL;
 	}
 
 	/**
-	 * For API
 	 * @param int $id category/feed/tag ID
-	 * @return array<string>|false
+	 * @return array<numeric-string>|false
 	 */
 	public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
 		string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null) {
@@ -1225,7 +1224,7 @@ SQL;
 		$stm = $this->pdo->prepare($sql);
 		$stm->execute($values);
 
-		return $stm->fetchAll(PDO::FETCH_COLUMN, 0) ?: [];
+		return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
 	}
 
 	/**

+ 18 - 9
app/Models/TagDAO.php

@@ -208,10 +208,7 @@ SQL;
 		return isset($tag[0]) ? $tag[0] : null;
 	}
 
-	/**
-	 * @return FreshRSS_Tag|null
-	 */
-	public function searchByName($name) {
+	public function searchByName(string $name): ?FreshRSS_Tag {
 		$sql = 'SELECT * FROM `_tag` WHERE name=?';
 		$stm = $this->pdo->prepare($sql);
 		$values = array($name);
@@ -342,7 +339,10 @@ SQL;
 		}
 	}
 
-	public function getTagsForEntry(int $id_entry) {
+	/**
+	 * @return array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}>|false
+	 */
+	public function getTagsForEntry(string $id_entry) {
 		$sql = 'SELECT t.id, t.name, et.id_entry IS NOT NULL as checked '
 			 . 'FROM `_tag` t '
 			 . 'LEFT OUTER JOIN `_entrytag` et ON et.id_tag = t.id AND et.id_entry=? '
@@ -368,7 +368,11 @@ SQL;
 		}
 	}
 
-	public function getTagsForEntries($entries) {
+	/**
+	 * @param array<FreshRSS_Entry|numeric-string|array<string,string>> $entries
+	 * @return array<array{'id_entry':string,'id_tag':int,'name':string}>|false
+	 */
+	public function getTagsForEntries(array $entries) {
 		$sql = 'SELECT et.id_entry, et.id_tag, t.name '
 			 . 'FROM `_tag` t '
 			 . 'INNER JOIN `_entrytag` et ON et.id_tag = t.id';
@@ -412,8 +416,12 @@ SQL;
 		}
 	}
 
-	//For API
-	public function getEntryIdsTagNames($entries) {
+	/**
+	 * For API
+	 * @param array<FreshRSS_Entry|numeric-string> $entries
+	 * @return array<string,array<string>>
+	 */
+	public function getEntryIdsTagNames(array $entries): array {
 		$result = array();
 		foreach ($this->getTagsForEntries($entries) as $line) {
 			$entryId = 'e_' . $line['id_entry'];
@@ -426,7 +434,8 @@ SQL;
 		return $result;
 	}
 
-	public static function daoToTag($listDAO) {
+	/** @return array<FreshRSS_Tag> */
+	private static function daoToTag($listDAO) {
 		$list = array();
 		if (!is_array($listDAO)) {
 			$listDAO = array($listDAO);

+ 2 - 2
app/Models/UserConfiguration.php

@@ -21,11 +21,11 @@
  * @property string $show_feed_name
  * @property bool $display_posts
  * @property string $email_validation_token
- * @property-read string $enabled
+ * @property-read bool $enabled
  * @property string $feverKey
  * @property bool $hide_read_feeds
  * @property int $html5_notif_timeout
- * @property-read string $is_admin
+ * @property-read bool $is_admin
  * @property int|null $keep_history_default
  * @property string $language
  * @property string $timezone

+ 71 - 9
app/Models/View.php

@@ -3,8 +3,11 @@
 class FreshRSS_View extends Minz_View {
 
 	// Main views
+	/** @var callable */
 	public $callbackBeforeEntries;
+	/** @var callable|null */
 	public $callbackBeforeFeeds;
+	/** @var callable */
 	public $callbackBeforePagination;
 	/** @var array<FreshRSS_Category> */
 	public $categories;
@@ -22,80 +25,115 @@ class FreshRSS_View extends Minz_View {
 	public $feeds;
 	/** @var int */
 	public $nbUnreadTags;
+	/** @var array<FreshRSS_Tag> */
 	public $tags;
+	/** @var array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}> */
+	public $tagsForEntry;
+	/** @var array<string,array<string>> */
+	public $tagsForEntries;
 	/** @var array<string,string> */
 	public $notification;
 	/** @var bool */
 	public $excludeMutedFeeds;
 
 	// Substriptions
+	/** @var FreshRSS_Category|null */
 	public $default_category;
+	/** @var bool */
 	public $displaySlider;
+	/** @var bool */
 	public $load_ok;
+	/** @var bool */
 	public $onlyFeedsWithError;
+	/** @var bool */
 	public $signalError;
 
 	// Manage users
+	/** @var array<string,string|int|bool> */
 	public $details;
+	/** @var bool */
 	public $disable_aside;
+	/** @var bool */
 	public $show_email_field;
 	/** @var string */
 	public $username;
+	/** @var array<array{'last_user_activity':int, 'language':string,'enabled':bool,'is_admin':bool, 'enabled':bool, 'article_count':int, 'database_size':int, 'last_user_activity', 'mail_login':string, 'feed_count':int, 'is_default':bool}>  */
 	public $users;
 
 	// Updates
+	/** @var string */
 	public $last_update_time;
+	/** @var array<string,bool> */
 	public $status_files;
+	/** @var array<string,bool> */
 	public $status_php;
+	/** @var bool */
 	public $update_to_apply;
+	/** @var array<string,bool> */
 	public $status_database;
 
 	// Archiving
+	/** @var int|false */
 	public $nb_total;
+	/** @var int */
 	public $size_total;
+	/** @var int */
 	public $size_user;
 
 	// Display
+	/** @var array<string> */
 	public $themes;
 
 	// Shortcuts
+	/** @var array<int, string> */
 	public $list_keys;
 
 	// User queries
-	/**
-	 * @var array<int,FreshRSS_UserQuery>
-	 */
+	/** @var array<int,FreshRSS_UserQuery> */
 	public $queries;
-	/**
-	 * @var FreshRSS_UserQuery|null
-	 */
+	/**  @var FreshRSS_UserQuery|null */
 	public $query;
 
 	// Export / Import
+	/** @var string */
 	public $content;
+	/** @var array<string,array<string>> */
 	public $entryIdsTagNames;
+	/** @var string */
 	public $list_title;
+	/** @var int */
 	public $queryId;
+	/** @var string */
 	public $type;
 
 	// Form login
+	/** @var int */
 	public $cookie_days;
+	/** @var string */
 	public $nonce;
+	/** @var string */
 	public $salt1;
 
 	// Registration
+	/** @var bool */
 	public $can_register;
+	/** @var string */
 	public $preferred_language;
+	/** @var bool */
 	public $show_tos_checkbox;
+	/** @var string */
 	public $terms_of_service;
-
-	// Email validation
+	/** @var string */
 	public $site_title;
+	/** @var string */
 	public $validation_url;
 
 	// Logs
+	/** @var int */
 	public $currentPage;
+	/** @var Minz_Paginator */
 	public $logsPaginator;
+	/** @var int */
 	public $nbPage;
 
 	// RSS view
@@ -105,12 +143,15 @@ class FreshRSS_View extends Minz_View {
 	public $rss_url = '';
 	/** @var string */
 	public $rss_base = '';
-	/** @var boolean */
+	/** @var bool */
 	public $internal_rendering = false;
 
 	// Content preview
+	/** @var string */
 	public $fatalError;
+	/** @var string */
 	public $htmlContent;
+	/** @var bool */
 	public $selectorSuccess;
 
 	// Extensions
@@ -126,28 +167,49 @@ class FreshRSS_View extends Minz_View {
 	public $extensions_installed;
 
 	// Errors
+	/** @var string */
 	public $code;
+	/** @var string */
 	public $errorMessage;
+	/** @var array<string,string> */
 	public $message;
 
 	// Statistics
+	/** @var float */
 	public $average;
+	/** @var float */
 	public $averageDayOfWeek;
+	/** @var float */
 	public $averageHour;
+	/** @var float */
 	public $averageMonth;
+	/** @var array<string> */
 	public $days;
+	/** @var array<string,array<int,int|string>> */
 	public $entryByCategory;
+	/** @var array<int,int> */
 	public $entryCount;
+	/** @var array<string,array<int,int|string>> */
 	public $feedByCategory;
+	/** @var array<int, string> */
 	public $hours24Labels;
+	/** @var array<string,array<int,array<string,int|string>>> */
 	public $idleFeeds;
+	/** @var array<int,string> */
 	public $last30DaysLabel;
+	/** @var array<int,string> */
 	public $last30DaysLabels;
+	/** @var array<string,string> */
 	public $months;
+	/** @var array<string,array<string,int>>|array<string,int> */
 	public $repartition;
+	/** @var array<int,int> */
 	public $repartitionDayOfWeek;
+	/** @var array<string,int>|array<int,int> */
 	public $repartitionHour;
+	/** @var array<int,int> */
 	public $repartitionMonth;
+	/** @var array<array<string,int|string>> */
 	public $topFeed;
 
 }

+ 2 - 4
app/Services/ExportService.php

@@ -75,9 +75,7 @@ class FreshRSS_Export_Service {
 
 		$view->list_title = _t('sub.import_export.starred_list');
 		$view->type = 'starred';
-		$entriesId = $this->entry_dao->listIdsWhere(
-			$type, 0, FreshRSS_Entry::STATE_ALL, 'ASC', -1
-		);
+		$entriesId = $this->entry_dao->listIdsWhere($type, 0, FreshRSS_Entry::STATE_ALL, 'ASC', -1) ?: [];
 		$view->entryIdsTagNames = $this->tag_dao->getEntryIdsTagNames($entriesId);
 		// The following is a streamable query, i.e. must be last
 		$view->entries = $this->entry_dao->listWhere(
@@ -115,7 +113,7 @@ class FreshRSS_Export_Service {
 		$view->type = 'feed/' . $feed->id();
 		$entriesId = $this->entry_dao->listIdsWhere(
 			'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC', $max_number_entries
-		);
+		) ?: [];
 		$view->entryIdsTagNames = $this->tag_dao->getEntryIdsTagNames($entriesId);
 		// The following is a streamable query, i.e. must be last
 		$view->entries = $this->entry_dao->listWhere(

+ 2 - 1
app/views/configure/display.phtml

@@ -49,7 +49,8 @@
 			<div class="group-controls">
 				<ul class="slides">
 					<?php $slides = count($this->themes); $i = 1; $themeAvailable = false; ?>
-					<?php foreach($this->themes as $theme) { ?>
+					<?php  /** @var array{'id':string, 'deprecated':bool, 'author':string, 'name':string, 'description':string} $theme */
+						foreach($this->themes as $theme) { ?>
 						<?php if (FreshRSS_Context::$user_conf->theme === $theme['id']) {
 							$checked = 'checked="checked"';
 							$themeAvailable = true;

+ 4 - 4
app/views/entry/read.phtml

@@ -1,8 +1,8 @@
-<?php /** @var FreshRSS_View $this */ ?>
 <?php
+/** @var FreshRSS_View $this */
 header('Content-Type: application/json; charset=UTF-8');
 
 FreshRSS::loadStylesAndScripts();
-echo json_encode(array(
-		'tags' => $this->tags,
-	));
+echo json_encode([
+		'tags' => $this->tagsForEntries,
+	]);

+ 2 - 2
app/views/index/logs.phtml

@@ -14,7 +14,7 @@
 	?>
 
 	<?php if (!empty($items)) { ?>
-	<?php $this->logsPaginator->render('logs_pagination.phtml', 'page'); ?>
+	<?php $this->logsPaginator->render('logs_pagination.phtml', 0); ?>
 	<div id="loglist-wrapper" class="table-wrapper">
 		<table id="loglist">
 			<thead>
@@ -41,7 +41,7 @@
 		</tbody>
 		</table>
 	</div>
-	<?php $this->logsPaginator->render('logs_pagination.phtml', 'page'); ?>
+	<?php $this->logsPaginator->render('logs_pagination.phtml', 0); ?>
 
 
 

+ 1 - 1
app/views/tag/getTagsForEntry.phtml

@@ -1,3 +1,3 @@
 <?php /** @var FreshRSS_View $this */ ?>
 <?php
-echo json_encode($this->tags);
+echo json_encode($this->tagsForEntry);

+ 79 - 75
lib/Minz/View.php

@@ -8,24 +8,29 @@
  * The Minz_View represents a view in the MVC paradigm
  */
 class Minz_View {
-	const VIEWS_PATH_NAME = '/views';
-	const LAYOUT_PATH_NAME = '/layout/';
-	const LAYOUT_DEFAULT = 'layout';
+	private const VIEWS_PATH_NAME = '/views';
+	private const LAYOUT_PATH_NAME = '/layout/';
+	private const LAYOUT_DEFAULT = 'layout';
 
+	/** @var string */
 	private $view_filename = '';
+	/** @var string */
 	private $layout_filename = '';
-
+	/** @var array<string> */
 	private static $base_pathnames = array(APP_PATH);
+	/** @var string */
 	private static $title = '';
-	private static $styles = array ();
-	private static $scripts = array ();
+	/** @var array<array{'media':string,'url':string}> */
+	private static $styles = [];
+	/** @var array<array{'url':string,'id':string,'defer':string,'async':string}> */
+	private static $scripts = [];
+	/** @var string|array{'dark'?:string,'light'?:string,'default'?:string} */
 	private static $themeColors;
-
-	private static $params = array ();
+	/** @var array<string,mixed> */
+	private static $params = [];
 
 	/**
-	 * Constructeur
-	 * Détermine si on utilise un layout ou non
+	 * Determines if a layout is used or not
 	 */
 	public function __construct() {
 		$this->_layout(self::LAYOUT_DEFAULT);
@@ -34,9 +39,9 @@ class Minz_View {
 	}
 
 	/**
-	 * [deprecated] Change the view file based on controller and action.
+	 * @deprecated Change the view file based on controller and action.
 	 */
-	public function change_view($controller_name, $action_name) {
+	public function change_view(string $controller_name, string $action_name): void {
 		Minz_Log::warning('Minz_View::change_view is deprecated, it will be removed in a future version. Please use Minz_View::_path instead.');
 		$this->_path($controller_name. '/' . $action_name . '.phtml');
 	}
@@ -46,7 +51,7 @@ class Minz_View {
 	 *
 	 * @param string $path the new path
 	 */
-	public function _path($path) {
+	public function _path(string $path): void {
 		$this->view_filename = self::VIEWS_PATH_NAME . '/' . $path;
 	}
 
@@ -57,14 +62,14 @@ class Minz_View {
 	 *
 	 * @param string $base_pathname the new base pathname.
 	 */
-	public static function addBasePathname($base_pathname) {
+	public static function addBasePathname(string $base_pathname): void {
 		array_unshift(self::$base_pathnames, $base_pathname);
 	}
 
 	/**
-	 * Construit la vue
+	 * Builds the view filename based on controller and action.
 	 */
-	public function build () {
+	public function build(): void {
 		if ($this->layout_filename !== '') {
 			$this->buildLayout ();
 		} else {
@@ -80,7 +85,7 @@ class Minz_View {
 	 * @param string $filename the name of the file to include.
 	 * @return boolean true if the file has been included, false else.
 	 */
-	private function includeFile($filename) {
+	private function includeFile(string $filename): bool {
 		// We search the filename in the list of base pathnames. Only the first view
 		// found is considered.
 		foreach (self::$base_pathnames as $base) {
@@ -95,9 +100,9 @@ class Minz_View {
 	}
 
 	/**
-	 * Construit le layout
+	 * Builds the layout
 	 */
-	public function buildLayout () {
+	public function buildLayout(): void {
 		header('Content-Type: text/html; charset=UTF-8');
 		if (!$this->includeFile($this->layout_filename)) {
 			Minz_Log::notice('File not found: `' . $this->layout_filename . '`');
@@ -105,9 +110,9 @@ class Minz_View {
 	}
 
 	/**
-	 * Affiche la Vue en elle-même
+	 * Displays the View itself
 	 */
-	public function render () {
+	public function render(): void {
 		if (!$this->includeFile($this->view_filename)) {
 			Minz_Log::notice('File not found: `' . $this->view_filename . '`');
 		}
@@ -116,14 +121,14 @@ class Minz_View {
 	public function renderToString(): string {
 		ob_start();
 		$this->render();
-		return ob_get_clean();
+		return ob_get_clean() ?: '';
 	}
 
 	/**
-	 * Ajoute un élément du layout
-	 * @param string $part l'élément partial à ajouter
+	 * Adds a layout element
+	 * @param string $part the partial element to be added
 	 */
-	public function partial ($part) {
+	public function partial(string $part): void {
 		$fic_partial = self::LAYOUT_PATH_NAME . '/' . $part . '.phtml';
 		if (!$this->includeFile($fic_partial)) {
 			Minz_Log::warning('File not found: `' . $fic_partial . '`');
@@ -131,10 +136,10 @@ class Minz_View {
 	}
 
 	/**
-	 * Affiche un élément graphique situé dans APP./views/helpers/
-	 * @param string $helper l'élément à afficher
+	 * Displays a graphic element located in APP./views/helpers/
+	 * @param string $helper the element to be displayed
 	 */
-	public function renderHelper ($helper) {
+	public function renderHelper(string $helper): void {
 		$fic_helper = '/views/helpers/' . $helper . '.phtml';
 		if (!$this->includeFile($fic_helper)) {
 			Minz_Log::warning('File not found: `' . $fic_helper . '`');
@@ -142,20 +147,20 @@ class Minz_View {
 	}
 
 	/**
-	 * Retourne renderHelper() dans une chaîne
-	 * @param string $helper l'élément à traîter
+	 * Returns renderHelper() in a string
+	 * @param string $helper the element to be treated
 	 */
-	public function helperToString($helper) {
+	public function helperToString(string $helper): string {
 		ob_start();
 		$this->renderHelper($helper);
-		return ob_get_clean();
+		return ob_get_clean() ?: '';
 	}
 
 	/**
 	 * Choose the current view layout.
 	 * @param string|false $layout the layout name to use, false to use no layouts.
 	 */
-	public function _layout($layout) {
+	public function _layout($layout): void {
 		if ($layout) {
 			$this->layout_filename = self::LAYOUT_PATH_NAME . $layout . '.phtml';
 		} else {
@@ -168,7 +173,7 @@ class Minz_View {
 	 * @deprecated Please use the `_layout` function instead.
 	 * @param bool $use true if we want to use the layout, false else
 	 */
-	public function _useLayout ($use) {
+	public function _useLayout(bool $use): void {
 		Minz_Log::warning('Minz_View::_useLayout is deprecated, it will be removed in a future version. Please use Minz_View::_layout instead.');
 		if ($use) {
 			$this->_layout(self::LAYOUT_DEFAULT);
@@ -178,60 +183,57 @@ class Minz_View {
 	}
 
 	/**
-	 * Gestion du titre
+	 * Title management
 	 */
-	public static function title () {
+	public static function title(): string {
 		return self::$title;
 	}
-	public static function headTitle () {
+	public static function headTitle(): string {
 		return '<title>' . self::$title . '</title>' . "\n";
 	}
-	public static function _title ($title) {
+	public static function _title(string $title): void {
 		self::$title = $title;
 	}
-	public static function prependTitle ($title) {
+	public static function prependTitle(string $title): void {
 		self::$title = $title . self::$title;
 	}
-	public static function appendTitle ($title) {
+	public static function appendTitle(string $title): void {
 		self::$title = self::$title . $title;
 	}
 
 	/**
-	 * Gestion des feuilles de style
+	 * Style sheet management
 	 */
-	public static function headStyle () {
+	public static function headStyle(): string {
 		$styles = '';
-
 		foreach(self::$styles as $style) {
 			$styles .= '<link rel="stylesheet" ' .
 				($style['media'] === 'all' ? '' : 'media="' . $style['media'] . '" ') .
 				'href="' . $style['url'] . '" />';
-
 			$styles .= "\n";
 		}
 
 		return $styles;
 	}
+
 	/**
 	 * Prepends a <link> element referencing stylesheet.
-	 *
-	 * @param string $url
-	 * @param string $media
-	 * @param bool $cond Conditional comment for IE, now deprecated and ignored
+	 * @param bool $cond Conditional comment for IE, now deprecated and ignored @deprecated
 	 */
-	public static function prependStyle($url, $media = 'all', $cond = false) {
+	public static function prependStyle(string $url, string $media = 'all', bool $cond = false): void {
 		array_unshift (self::$styles, array (
 			'url' => $url,
 			'media' => $media,
 		));
 	}
+
 	/**
 	 * Append a `<link>` element referencing stylesheet.
 	 * @param string $url
 	 * @param string $media
-	 * @param bool $cond Conditional comment for IE, now deprecated and ignored
+	 * @param bool $cond Conditional comment for IE, now deprecated and ignored @deprecated
 	 */
-	public static function appendStyle($url, $media = 'all', $cond = false) {
+	public static function appendStyle(string $url, string $media = 'all', bool $cond = false): void {
 		self::$styles[] = array (
 			'url' => $url,
 			'media' => $media,
@@ -239,7 +241,7 @@ class Minz_View {
 	}
 
 	/**
-	 * @param array|string $themeColors
+	 * @param string|array{'dark'?:string,'light'?:string,'default'?:string} $themeColors
 	 */
 	public static function appendThemeColors($themeColors): void {
 		self::$themeColors = $themeColors;
@@ -250,29 +252,27 @@ class Minz_View {
 	 */
 	public static function metaThemeColor(): string {
 		$meta = '';
-
-		if (!empty(self::$themeColors['light'])) {
-			$meta .= '<meta name="theme-color" media="(prefers-color-scheme: light)" content="' . htmlspecialchars(self::$themeColors['light']) . '" />';
-		}
-		if (!empty(self::$themeColors['dark'])) {
-			$meta .= '<meta name="theme-color" media="(prefers-color-scheme: dark)" content="' . htmlspecialchars(self::$themeColors['dark']) . '" />';
-		}
-		if (!empty(self::$themeColors['default'])) {
-			$meta .= '<meta name="theme-color" content="' . htmlspecialchars(self::$themeColors['default']) . '" />';
-		}
-		if (empty(self::$themeColors['default']) && !empty(self::$themeColors) && empty(self::$themeColors['light']) && empty(self::$themeColors['dark'])) {
+		if (is_array(self::$themeColors)) {
+			if (!empty(self::$themeColors['light'])) {
+				$meta .= '<meta name="theme-color" media="(prefers-color-scheme: light)" content="' . htmlspecialchars(self::$themeColors['light']) . '" />';
+			}
+			if (!empty(self::$themeColors['dark'])) {
+				$meta .= '<meta name="theme-color" media="(prefers-color-scheme: dark)" content="' . htmlspecialchars(self::$themeColors['dark']) . '" />';
+			}
+			if (!empty(self::$themeColors['default'])) {
+				$meta .= '<meta name="theme-color" content="' . htmlspecialchars(self::$themeColors['default']) . '" />';
+			}
+		} elseif (is_string(self::$themeColors)) {
 			$meta .= '<meta name="theme-color" content="' . htmlspecialchars(self::$themeColors) . '" />';
 		}
-
 		return $meta;
 	}
 
 	/**
-	 * Gestion des scripts JS
+	 * JS script management
 	 */
-	public static function headScript () {
+	public static function headScript(): string {
 		$scripts = '';
-
 		foreach (self::$scripts as $script) {
 			$scripts .= '<script src="' . $script['url'] . '"';
 			if (!empty($script['id'])) {
@@ -293,12 +293,12 @@ class Minz_View {
 	/**
 	 * Prepend a `<script>` element.
 	 * @param string $url
-	 * @param bool $cond Conditional comment for IE, now deprecated and ignored
+	 * @param bool $cond Conditional comment for IE, now deprecated and ignored @deprecated
 	 * @param bool $defer Use `defer` flag
 	 * @param bool $async Use `async` flag
 	 * @param string $id Add a script `id` attribute
 	 */
-	public static function prependScript($url, $cond = false, $defer = true, $async = true, $id = '') {
+	public static function prependScript(string $url, bool $cond = false, bool $defer = true, bool $async = true, string $id = ''): void {
 		array_unshift(self::$scripts, array (
 			'url' => $url,
 			'defer' => $defer,
@@ -306,15 +306,16 @@ class Minz_View {
 			'id' => $id,
 		));
 	}
-/**
+
+	/**
 	 * Append a `<script>` element.
 	 * @param string $url
-	 * @param bool $cond Conditional comment for IE, now deprecated and ignored
+	 * @param bool $cond Conditional comment for IE, now deprecated and ignored @deprecated
 	 * @param bool $defer Use `defer` flag
 	 * @param bool $async Use `async` flag
 	 * @param string $id Add a script `id` attribute
 	 */
-	public static function appendScript($url, $cond = false, $defer = true, $async = true, $id = '') {
+	public static function appendScript(string $url, bool $cond = false, bool $defer = true, bool $async = true, string $id = ''): void {
 		self::$scripts[] = array (
 			'url' => $url,
 			'defer' => $defer,
@@ -324,12 +325,15 @@ class Minz_View {
 	}
 
 	/**
-	 * Gestion des paramètres ajoutés à la vue
+	 * Management of parameters added to the view
+	 * @param string $key
+	 * @param mixed $value
 	 */
-	public static function _param ($key, $value) {
+	public static function _param(string $key, $value): void {
 		self::$params[$key] = $value;
 	}
-	public function attributeParams () {
+
+	public function attributeParams(): void {
 		foreach (Minz_View::$params as $key => $value) {
 			$this->$key = $value;
 		}

+ 0 - 3
p/api/greader.php

@@ -558,9 +558,6 @@ final class GReaderAPI {
 
 		$tagDAO = FreshRSS_Factory::createTagDao();
 		$entryIdsTagNames = $tagDAO->getEntryIdsTagNames($entries);
-		if ($entryIdsTagNames == false) {
-			$entryIdsTagNames = array();
-		}
 
 		$items = array();
 		foreach ($entries as $item) {

+ 0 - 2
tests/phpstan-next.txt

@@ -18,7 +18,6 @@
 ./app/Models/Share.php
 ./app/Models/TagDAO.php
 ./app/Models/Themes.php
-./app/Models/View.php
 ./app/Services/ExportService.php
 ./app/Services/ImportService.php
 ./cli/i18n/I18nData.php
@@ -32,4 +31,3 @@
 ./lib/Minz/Paginator.php
 ./lib/Minz/Session.php
 ./lib/Minz/Translate.php
-./lib/Minz/View.php