aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2024-12-27 12:12:49 +0100
committerGravatar GitHub <noreply@github.com> 2024-12-27 12:12:49 +0100
commitb1d24fbdb7d1cc948c946295035dad6df550fb7e (patch)
tree7b4365a04097a779659474fbb9281a9661512522 /lib
parent897e4a3f4a273d50c28157edb67612b2d7fa2e6f (diff)
PHPStan 2.0 (#7131)
* PHPStan 2.0 fix https://github.com/FreshRSS/FreshRSS/issues/6989 https://github.com/phpstan/phpstan/releases/tag/2.0.0 https://github.com/phpstan/phpstan/blob/2.0.x/UPGRADING.md * More * More * Done * fix i18n CLI * Restore a PHPStan Next test For work towards PHPStan Level 10 * 4 more on Level 10 * fix getTagsForEntry * API at Level 10 * More Level 10 * Finish Minz at Level 10 * Finish CLI at Level 10 * Finish Controllers at Level 10 * More Level 10 * More * Pass bleedingEdge * Clean PHPStan options and add TODOs * Level 10 for main config * More * Consitency array vs. list * Sanitize themes get_infos * Simplify TagDAO->getTagsForEntries() * Finish reportAnyTypeWideningInVarTag * Prepare checkBenevolentUnionTypes and checkImplicitMixed * Fixes * Refix * Another fix * Casing of __METHOD__ constant
Diffstat (limited to 'lib')
-rw-r--r--lib/Minz/Configuration.php5
-rw-r--r--lib/Minz/Error.php14
-rw-r--r--lib/Minz/Extension.php6
-rw-r--r--lib/Minz/ExtensionManager.php6
-rw-r--r--lib/Minz/FrontController.php2
-rw-r--r--lib/Minz/Migrator.php5
-rw-r--r--lib/Minz/ModelArray.php2
-rw-r--r--lib/Minz/ModelPdo.php16
-rw-r--r--lib/Minz/Paginator.php14
-rw-r--r--lib/Minz/Request.php67
-rw-r--r--lib/Minz/Session.php19
-rw-r--r--lib/Minz/Translate.php16
-rw-r--r--lib/Minz/Url.php14
-rw-r--r--lib/Minz/View.php8
-rw-r--r--lib/favicons.php9
-rw-r--r--lib/http-conditional.php22
-rw-r--r--lib/lib_rss.php78
17 files changed, 190 insertions, 113 deletions
diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php
index b2742e249..b56268b4a 100644
--- a/lib/Minz/Configuration.php
+++ b/lib/Minz/Configuration.php
@@ -45,7 +45,7 @@ class Minz_Configuration {
*/
public static function load(string $filename): array {
$data = @include($filename);
- if (is_array($data)) {
+ if (is_array($data) && is_array_keys_string($data)) {
return $data;
} else {
throw new Minz_FileNotExistException($filename);
@@ -117,9 +117,10 @@ class Minz_Configuration {
}
try {
- $this->data = array_replace_recursive(
+ $overloaded = array_replace_recursive(
$this->data, self::load($this->config_filename)
);
+ $this->data = array_filter($overloaded, 'is_string', ARRAY_FILTER_USE_KEY);
} catch (Minz_FileNotExistException $e) {
if ($this->default_filename == null) {
throw $e;
diff --git a/lib/Minz/Error.php b/lib/Minz/Error.php
index 74a71de0a..e95fd346c 100644
--- a/lib/Minz/Error.php
+++ b/lib/Minz/Error.php
@@ -15,13 +15,13 @@ class Minz_Error {
/**
* Permet de lancer une erreur
* @param int $code le type de l'erreur, par défaut 404 (page not found)
- * @param string|array<'error'|'warning'|'notice',array<string>> $logs logs d'erreurs découpés de la forme
+ * @param string|array<'error'|'warning'|'notice',list<string>> $logs logs d'erreurs découpés de la forme
* > $logs['error']
* > $logs['warning']
* > $logs['notice']
* @param bool $redirect indique s'il faut forcer la redirection (les logs ne seront pas transmis)
*/
- public static function error(int $code = 404, $logs = [], bool $redirect = true): void {
+ public static function error(int $code = 404, string|array $logs = [], bool $redirect = true): void {
$logs = self::processLogs($logs);
$error_filename = APP_PATH . '/Controllers/errorController.php';
@@ -49,8 +49,8 @@ class Minz_Error {
/**
* Returns filtered logs
- * @param string|array<'error'|'warning'|'notice',array<string>> $logs logs sorted by category (error, warning, notice)
- * @return array<string> list of matching logs, without the category, according to environment preferences (production / development)
+ * @param string|array<'error'|'warning'|'notice',list<string>> $logs logs sorted by category (error, warning, notice)
+ * @return list<string> list of matching logs, without the category, according to environment preferences (production / development)
*/
private static function processLogs($logs): array {
if (is_string($logs)) {
@@ -61,13 +61,13 @@ class Minz_Error {
$warning = [];
$notice = [];
- if (isset($logs['error']) && is_array($logs['error'])) {
+ if (is_array($logs['error'] ?? null)) {
$error = $logs['error'];
}
- if (isset($logs['warning']) && is_array($logs['warning'])) {
+ if (is_array($logs['warning'] ?? null)) {
$warning = $logs['warning'];
}
- if (isset($logs['notice']) && is_array($logs['notice'])) {
+ if (is_array($logs['notice'] ?? null)) {
$notice = $logs['notice'];
}
diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php
index 69b9c569c..25cbe2091 100644
--- a/lib/Minz/Extension.php
+++ b/lib/Minz/Extension.php
@@ -26,7 +26,7 @@ abstract class Minz_Extension {
private bool $is_enabled;
- /** @var string[] */
+ /** @var array<string,string> */
protected array $csp_policies = [];
/**
@@ -411,11 +411,11 @@ abstract class Minz_Extension {
}
/**
- * @param string[] $policies
+ * @param array<string,string> $policies
*/
public function amendCsp(array &$policies): void {
foreach ($this->csp_policies as $policy => $source) {
- if (array_key_exists($policy, $policies)) {
+ if (isset($policies[$policy])) {
$policies[$policy] .= ' ' . $source;
} else {
$policies[$policy] = $source;
diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php
index 90f005d29..84a6fc09f 100644
--- a/lib/Minz/ExtensionManager.php
+++ b/lib/Minz/ExtensionManager.php
@@ -136,7 +136,6 @@ final class Minz_ExtensionManager {
array_walk($list_core_extensions, function (&$s) { $s = CORE_EXTENSIONS_PATH . '/' . $s; });
array_walk($list_thirdparty_extensions, function (&$s) { $s = THIRDPARTY_EXTENSIONS_PATH . '/' . $s; });
- /** @var array<string> */
$list_potential_extensions = array_merge($list_core_extensions, $list_thirdparty_extensions);
$system_conf = Minz_Configuration::get('system');
@@ -403,7 +402,10 @@ final class Minz_ExtensionManager {
public static function callHookString(string $hook_name): string {
$result = '';
foreach (self::$hook_list[$hook_name]['list'] ?? [] as $function) {
- $result = $result . call_user_func($function);
+ $return = call_user_func($function);
+ if (is_scalar($return)) {
+ $result .= $return;
+ }
}
return $result;
}
diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php
index 3a86d2d6d..756885ffe 100644
--- a/lib/Minz/FrontController.php
+++ b/lib/Minz/FrontController.php
@@ -42,7 +42,7 @@ class Minz_FrontController {
$url = Minz_Url::build();
$url['params'] = array_merge(
empty($url['params']) || !is_array($url['params']) ? [] : $url['params'],
- $_POST
+ array_filter($_POST, 'is_string', ARRAY_FILTER_USE_KEY)
);
Minz_Request::forward($url);
} catch (Minz_Exception $e) {
diff --git a/lib/Minz/Migrator.php b/lib/Minz/Migrator.php
index 39c834765..47df98e7f 100644
--- a/lib/Minz/Migrator.php
+++ b/lib/Minz/Migrator.php
@@ -176,6 +176,7 @@ class Minz_Migrator
public function migrations(): array {
$migrations = $this->migrations;
uksort($migrations, 'strnatcmp');
+ /** @var array<string,callable> $migrations */
return $migrations;
}
@@ -237,7 +238,7 @@ class Minz_Migrator
* considered as successful. It is considered as good practice to return
* true on success though.
*
- * @return array<string|bool> Return the results of each executed migration. If an
+ * @return array<string,bool|string> Return the results of each executed migration. If an
* exception was raised in a migration, its result is set to
* the exception message.
*/
@@ -251,7 +252,7 @@ class Minz_Migrator
try {
$migration_result = $callback();
- $result[$version] = $migration_result;
+ $result[$version] = (bool)$migration_result;
} catch (Exception $e) {
$migration_result = false;
$result[$version] = $e->getMessage();
diff --git a/lib/Minz/ModelArray.php b/lib/Minz/ModelArray.php
index 5a1d286cd..89f7f8da4 100644
--- a/lib/Minz/ModelArray.php
+++ b/lib/Minz/ModelArray.php
@@ -40,7 +40,7 @@ class Minz_ModelArray {
if ($data === false) {
throw new Minz_PermissionDeniedException($this->filename);
- } elseif (!is_array($data)) {
+ } elseif (!is_array($data) || !is_array_keys_string($data)) {
$data = [];
}
return $data;
diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php
index 26b5269a5..86f6df306 100644
--- a/lib/Minz/ModelPdo.php
+++ b/lib/Minz/ModelPdo.php
@@ -176,8 +176,8 @@ class Minz_ModelPdo {
/**
* @param array<string,int|string|null> $values
- * @phpstan-return ($mode is PDO::FETCH_ASSOC ? array<array<string,int|string|null>>|null : array<int|string|null>|null)
- * @return array<array<string,int|string|null>>|array<int|string|null>|null
+ * @phpstan-return ($mode is PDO::FETCH_ASSOC ? list<array<string,int|string|null>>|null : list<int|string|null>|null)
+ * @return list<array<string,int|string|null>>|list<int|string|null>|null
*/
private function fetchAny(string $sql, array $values, int $mode, int $column = 0): ?array {
$stm = $this->pdo->prepare($sql);
@@ -204,15 +204,15 @@ class Minz_ModelPdo {
switch ($mode) {
case PDO::FETCH_COLUMN:
$res = $stm->fetchAll(PDO::FETCH_COLUMN, $column);
+ /** @var list<int|string|null> $res */
break;
case PDO::FETCH_ASSOC:
default:
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ /** @var list<array<string,int|string|null>> $res */
break;
}
- if ($res !== false) {
- return $res;
- }
+ return $res;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);
@@ -231,7 +231,7 @@ class Minz_ModelPdo {
/**
* @param array<string,int|string|null> $values
- * @return array<array<string,int|string|null>>|null
+ * @return list<array<string,int|string|null>>|null
*/
public function fetchAssoc(string $sql, array $values = []): ?array {
return $this->fetchAny($sql, $values, PDO::FETCH_ASSOC);
@@ -239,7 +239,7 @@ class Minz_ModelPdo {
/**
* @param array<string,int|string|null> $values
- * @return array<int|string|null>|null
+ * @return list<int|string|null>|null
*/
public function fetchColumn(string $sql, int $column, array $values = []): ?array {
return $this->fetchAny($sql, $values, PDO::FETCH_COLUMN, $column);
@@ -257,6 +257,6 @@ class Minz_ModelPdo {
Minz_Log::error('SQL error ' . json_encode($stm->errorInfo()) . ' during ' . $sql);
return null;
}
- return isset($columns[0]) ? (string)$columns[0] : null;
+ return is_scalar($columns[0] ?? null) ? (string)$columns[0] : null;
}
}
diff --git a/lib/Minz/Paginator.php b/lib/Minz/Paginator.php
index 727fe42d3..7d6892c67 100644
--- a/lib/Minz/Paginator.php
+++ b/lib/Minz/Paginator.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
*/
class Minz_Paginator {
/**
- * @var array<Minz_Model> tableau des éléments à afficher/gérer
+ * @var list<Minz_Model> tableau des éléments à afficher/gérer
*/
private array $items = [];
@@ -37,7 +37,7 @@ class Minz_Paginator {
/**
* Constructeur
- * @param array<Minz_Model> $items les éléments à gérer
+ * @param list<Minz_Model> $items les éléments à gérer
*/
public function __construct(array $items) {
$this->_items($items);
@@ -116,10 +116,10 @@ class Minz_Paginator {
*/
/**
* @param bool $all si à true, retourne tous les éléments sans prendre en compte la pagination
- * @return array<Minz_Model>
+ * @return list<Minz_Model>
*/
public function items(bool $all = false): array {
- $array = array ();
+ $array = [];
$nbItems = $this->nbItems();
if ($nbItems <= $this->nbItemsPerPage || $all) {
@@ -129,9 +129,9 @@ class Minz_Paginator {
$counter = 0;
$i = 0;
- foreach ($this->items as $key => $item) {
+ foreach ($this->items as $item) {
if ($i >= $begin) {
- $array[$key] = $item;
+ $array[] = $item;
$counter++;
}
if ($counter >= $this->nbItemsPerPage) {
@@ -159,7 +159,7 @@ class Minz_Paginator {
/**
* SETTEURS
*/
- /** @param array<Minz_Model> $items */
+ /** @param list<Minz_Model> $items */
public function _items(?array $items): void {
$this->items = $items ?? [];
$this->_nbPage();
diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php
index bddb5a276..f441bcabf 100644
--- a/lib/Minz/Request.php
+++ b/lib/Minz/Request.php
@@ -69,18 +69,33 @@ class Minz_Request {
if (empty(self::$params[$key]) || !is_array(self::$params[$key])) {
return [];
}
- return $plaintext ? self::$params[$key] : Minz_Helper::htmlspecialchars_utf8(self::$params[$key]);
+ $result = [];
+ foreach (self::$params[$key] as $k => $v) {
+ if (is_string($v)) {
+ $result[$k] = $v;
+ } elseif (is_array($v)) {
+ $vs = [];
+ foreach ($v as $k2 => $v2) {
+ if (is_string($k2) && (is_string($v2) || is_int($v2) || is_bool($v2))) {
+ $vs[$k2] = $v2;
+ }
+ }
+ $result[$k] = $vs;
+ }
+ }
+ return $plaintext ? $result : Minz_Helper::htmlspecialchars_utf8($result);
}
/**
* @param bool $plaintext `true` to return special characters without any escaping (unsafe), `false` (default) to XML-encode them
- * @return array<string>
+ * @return list<string>
*/
public static function paramArrayString(string $key, bool $plaintext = false): array {
if (empty(self::$params[$key]) || !is_array(self::$params[$key])) {
return [];
}
$result = array_filter(self::$params[$key], 'is_string');
+ $result = array_values($result);
return $plaintext ? $result : Minz_Helper::htmlspecialchars_utf8($result);
}
@@ -143,7 +158,7 @@ class Minz_Request {
* character is used to break the text into lines. This method is well suited to use
* to split textarea content.
* @param bool $plaintext `true` to return special characters without any escaping (unsafe), `false` (default) to XML-encode them
- * @return array<string>
+ * @return list<string>
*/
public static function paramTextToArray(string $key, bool $plaintext = false): array {
if (isset(self::$params[$key]) && is_string(self::$params[$key])) {
@@ -214,7 +229,7 @@ class Minz_Request {
* Initialise la Request
*/
public static function init(): void {
- self::_params($_GET);
+ self::_params(array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY));
self::initJSON();
}
@@ -227,8 +242,8 @@ class Minz_Request {
* Return true if the request is over HTTPS, false otherwise (HTTP)
*/
public static function isHttps(): bool {
- $header = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
- if ('' != $header) {
+ $header = is_string($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null) ? $_SERVER['HTTP_X_FORWARDED_PROTO'] : '';
+ if ('' !== $header) {
return 'https' === strtolower($header);
}
return 'on' === ($_SERVER['HTTPS'] ?? '');
@@ -250,34 +265,37 @@ class Minz_Request {
}
private static function extractProtocol(): string {
- if (self::isHttps()) {
- return 'https';
- }
- return 'http';
+ return self::isHttps() ? 'https' : 'http';
}
private static function extractHost(): string {
- if ('' != $host = ($_SERVER['HTTP_X_FORWARDED_HOST'] ?? '')) {
+ $host = is_string($_SERVER['HTTP_X_FORWARDED_HOST'] ?? null) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : '';
+ if ($host !== '') {
return parse_url("http://{$host}", PHP_URL_HOST) ?: 'localhost';
}
- if ('' != $host = ($_SERVER['HTTP_HOST'] ?? '')) {
+ $host = is_string($_SERVER['HTTP_HOST'] ?? null) ? $_SERVER['HTTP_HOST'] : '';
+ if ($host !== '') {
// Might contain a port number, and mind IPv6 addresses
return parse_url("http://{$host}", PHP_URL_HOST) ?: 'localhost';
}
- if ('' != $host = ($_SERVER['SERVER_NAME'] ?? '')) {
+ $host = is_string($_SERVER['SERVER_NAME'] ?? null) ? $_SERVER['SERVER_NAME'] : '';
+ if ($host !== '') {
return $host;
}
return 'localhost';
}
private static function extractPort(): int {
- if ('' != $port = ($_SERVER['HTTP_X_FORWARDED_PORT'] ?? '')) {
+ $port = is_numeric($_SERVER['HTTP_X_FORWARDED_PORT'] ?? null) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : '';
+ if ($port !== '') {
return intval($port);
}
- if ('' != $proto = ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) {
+ $proto = is_string($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null) ? $_SERVER['HTTP_X_FORWARDED_PROTO'] : '';
+ if ($proto !== '') {
return 'https' === strtolower($proto) ? 443 : 80;
}
- if ('' != $port = ($_SERVER['SERVER_PORT'] ?? '')) {
+ $port = is_numeric($_SERVER['SERVER_PORT'] ?? null) ? $_SERVER['SERVER_PORT'] : '';
+ if ($port !== '') {
return intval($port);
}
return self::isHttps() ? 443 : 80;
@@ -294,15 +312,16 @@ class Minz_Request {
}
private static function extractPrefix(): string {
- if ('' != $prefix = ($_SERVER['HTTP_X_FORWARDED_PREFIX'] ?? '')) {
+ $prefix = is_string($_SERVER['HTTP_X_FORWARDED_PREFIX'] ?? null) ? $_SERVER['HTTP_X_FORWARDED_PREFIX'] : '';
+ if ($prefix !== '') {
return rtrim($prefix, '/ ');
}
return '';
}
private static function extractPath(): string {
- $path = $_SERVER['REQUEST_URI'] ?? '';
- if ($path != '') {
+ $path = is_string($_SERVER['REQUEST_URI'] ?? null) ? $_SERVER['REQUEST_URI'] : '';
+ if ($path !== '') {
$path = parse_url($path, PHP_URL_PATH) ?: '';
return substr($path, -1) === '/' ? rtrim($path, '/') : dirname($path);
}
@@ -356,7 +375,7 @@ class Minz_Request {
}
private static function requestId(): string {
- if (empty($_GET['rid']) || !ctype_xdigit($_GET['rid'])) {
+ if (!is_string($_GET['rid'] ?? null) || !ctype_xdigit($_GET['rid'])) {
$_GET['rid'] = uniqid();
}
return $_GET['rid'];
@@ -476,7 +495,8 @@ class Minz_Request {
}
private static function extractContentType(): string {
- return strtolower(trim($_SERVER['CONTENT_TYPE'] ?? ''));
+ $contentType = is_string($_SERVER['CONTENT_TYPE'] ?? null) ? $_SERVER['CONTENT_TYPE'] : '';
+ return strtolower(trim($contentType));
}
public static function isPost(): bool {
@@ -484,10 +504,11 @@ class Minz_Request {
}
/**
- * @return array<string>
+ * @return list<string>
*/
public static function getPreferredLanguages(): array {
- if (preg_match_all('/(^|,)\s*(?P<lang>[^;,]+)/', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '', $matches) > 0) {
+ $acceptLanguage = is_string($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
+ if (preg_match_all('/(^|,)\s*(?P<lang>[^;,]+)/', $acceptLanguage, $matches) > 0) {
return $matches['lang'];
}
return ['en'];
diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php
index 9977e62f6..bb2c1a817 100644
--- a/lib/Minz/Session.php
+++ b/lib/Minz/Session.php
@@ -72,7 +72,13 @@ class Minz_Session {
if (empty($_SESSION[$key]) || !is_array($_SESSION[$key])) {
return [];
}
- return $_SESSION[$key];
+ $result = [];
+ foreach ($_SESSION[$key] as $k => $v) {
+ if (is_string($v) || (is_array($v) && is_array_keys_string($v))) {
+ $result[$k] = $v;
+ }
+ }
+ return $result;
}
public static function paramTernary(string $key): ?bool {
@@ -97,10 +103,7 @@ class Minz_Session {
}
public static function paramInt(string $key): int {
- if (!empty($_SESSION[$key])) {
- return intval($_SESSION[$key]);
- }
- return 0;
+ return empty($_SESSION[$key]) || !is_numeric($_SESSION[$key]) ? 0 : (int)$_SESSION[$key];
}
public static function paramString(string $key): string {
@@ -175,10 +178,10 @@ class Minz_Session {
public static function getCookieDir(): string {
// Get the script_name (e.g. /p/i/index.php) and keep only the path.
$cookie_dir = '';
- if (!empty($_SERVER['HTTP_X_FORWARDED_PREFIX'])) {
+ if (!empty($_SERVER['HTTP_X_FORWARDED_PREFIX']) && is_string($_SERVER['HTTP_X_FORWARDED_PREFIX'])) {
$cookie_dir .= rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/ ');
}
- $cookie_dir .= empty($_SERVER['REQUEST_URI']) ? '/' : $_SERVER['REQUEST_URI'];
+ $cookie_dir .= empty($_SERVER['REQUEST_URI']) || !is_string($_SERVER['REQUEST_URI']) ? '/' : $_SERVER['REQUEST_URI'];
if (substr($cookie_dir, -1) !== '/') {
$cookie_dir = dirname($cookie_dir) . '/';
}
@@ -210,7 +213,7 @@ class Minz_Session {
}
public static function getLongTermCookie(string $name): string {
- return $_COOKIE[$name] ?? '';
+ return is_string($_COOKIE[$name] ?? null) ? $_COOKIE[$name] : '';
}
}
diff --git a/lib/Minz/Translate.php b/lib/Minz/Translate.php
index 08b3c91e8..58bf3f424 100644
--- a/lib/Minz/Translate.php
+++ b/lib/Minz/Translate.php
@@ -63,7 +63,7 @@ class Minz_Translate {
/**
* Return the list of available languages.
- * @return array<string> containing langs found in different registered paths.
+ * @return list<string> containing langs found in different registered paths.
*/
public static function availableLanguages(): array {
$list_langs = [];
@@ -81,7 +81,7 @@ class Minz_Translate {
}
}
- return array_unique($list_langs);
+ return array_values(array_unique($list_langs));
}
/**
@@ -197,7 +197,7 @@ class Minz_Translate {
Minz_Log::debug($key . ' is not in a valid format');
$top_level = 'gen';
} else {
- $top_level = array_shift($group);
+ $top_level = array_shift($group) ?? '';
}
// If $translates[$top_level] is null it means we have to load the
@@ -218,6 +218,9 @@ class Minz_Translate {
$level_processed = 0;
$translation_value = $key;
foreach ($group as $i18n_level) {
+ if (!is_array($translates)) {
+ continue; // Not needed. To help PHPStan
+ }
$level_processed++;
if (!isset($translates[$i18n_level])) {
Minz_Log::debug($key . ' is not a valid key');
@@ -231,10 +234,9 @@ class Minz_Translate {
}
}
- if (is_array($translation_value)) {
- if (isset($translation_value['_'])) {
- $translation_value = $translation_value['_'];
- } else {
+ if (!is_string($translation_value)) {
+ $translation_value = is_array($translation_value) ? ($translation_value['_'] ?? null) : null;
+ if (!is_string($translation_value)) {
Minz_Log::debug($key . ' is not a valid key');
return $key;
}
diff --git a/lib/Minz/Url.php b/lib/Minz/Url.php
index 3948414d8..811f0fff7 100644
--- a/lib/Minz/Url.php
+++ b/lib/Minz/Url.php
@@ -145,10 +145,16 @@ class Minz_Url {
* @return array{c?:string,a?:string,params?:array<string,string>} URL representation
*/
public static function build(): array {
+ $get = [];
+ foreach ($_GET as $key => $value) {
+ if (is_string($key) && is_string($value)) {
+ $get[$key] = $value;
+ }
+ }
$url = [
- 'c' => $_GET['c'] ?? Minz_Request::defaultControllerName(),
- 'a' => $_GET['a'] ?? Minz_Request::defaultActionName(),
- 'params' => $_GET,
+ 'c' => is_string($_GET['c'] ?? null) ? $_GET['c'] : Minz_Request::defaultControllerName(),
+ 'a' => is_string($_GET['a'] ?? null) ? $_GET['a'] : Minz_Request::defaultActionName(),
+ 'params' => $get,
];
// post-traitement
@@ -166,7 +172,7 @@ function _url(string $controller, string $action, int|string ...$args): string|f
return false;
}
- $params = array ();
+ $params = [];
for ($i = 0; $i < $nb_args; $i += 2) {
$arg = '' . $args[$i];
$params[$arg] = '' . $args[$i + 1];
diff --git a/lib/Minz/View.php b/lib/Minz/View.php
index f7dceef0a..01e8501b0 100644
--- a/lib/Minz/View.php
+++ b/lib/Minz/View.php
@@ -19,11 +19,11 @@ class Minz_View {
/** @var array<string> */
private static array $base_pathnames = [APP_PATH];
private static string $title = '';
- /** @var array<array{'media':string,'url':string}> */
+ /** @var array<array{media:string,url:string}> */
private static array $styles = [];
- /** @var array<array{'url':string,'id':string,'defer':bool,'async':bool}> */
+ /** @var array<array{url:string,id:string,defer:bool,async:bool}> */
private static array $scripts = [];
- /** @var string|array{'dark'?:string,'light'?:string,'default'?:string} */
+ /** @var string|array{dark?:string,light?:string,default?:string} */
private static $themeColors;
/** @var array<string,mixed> */
private static array $params = [];
@@ -245,7 +245,7 @@ class Minz_View {
}
/**
- * @param string|array{'dark'?:string,'light'?:string,'default'?:string} $themeColors
+ * @param string|array{dark?:string,light?:string,default?:string} $themeColors
*/
public static function appendThemeColors($themeColors): void {
self::$themeColors = $themeColors;
diff --git a/lib/favicons.php b/lib/favicons.php
index 5df3682b8..bcb05f4f2 100644
--- a/lib/favicons.php
+++ b/lib/favicons.php
@@ -22,7 +22,7 @@ function isImgMime(string $content): bool {
return $isImage;
}
-/** @param array<int,int|bool> $curlOptions */
+/** @param array<int,int|bool|string> $curlOptions */
function downloadHttp(string &$url, array $curlOptions = []): string {
syslog(LOG_INFO, 'FreshRSS Favicon GET ' . $url);
$url2 = checkUrl($url);
@@ -61,7 +61,7 @@ function downloadHttp(string &$url, array $curlOptions = []): string {
$url = $url2; //Possible redirect
}
}
- return $info['http_code'] == 200 ? $response : '';
+ return is_array($info) && $info['http_code'] == 200 ? $response : '';
}
function searchFavicon(string &$url): string {
@@ -103,6 +103,9 @@ function searchFavicon(string &$url): string {
}
$iri = $href->get_iri();
+ if ($iri == false) {
+ return '';
+ }
$favicon = downloadHttp($iri, [CURLOPT_REFERER => $url]);
if (isImgMime($favicon)) {
return $favicon;
@@ -115,7 +118,7 @@ function download_favicon(string $url, string $dest): bool {
$url = trim($url);
$favicon = searchFavicon($url);
if ($favicon == '') {
- $rootUrl = preg_replace('%^(https?://[^/]+).*$%i', '$1/', $url);
+ $rootUrl = preg_replace('%^(https?://[^/]+).*$%i', '$1/', $url) ?? $url;
if ($rootUrl != $url) {
$url = $rootUrl;
$favicon = searchFavicon($url);
diff --git a/lib/http-conditional.php b/lib/http-conditional.php
index f683d4fbf..c08f72f75 100644
--- a/lib/http-conditional.php
+++ b/lib/http-conditional.php
@@ -29,7 +29,7 @@ declare(strict_types=1);
?>
```
- Version 1.9, 2023-04-08, https://alexandre.alapetite.fr/doc-alex/php-http-304/
+ Version 1.10, 2024-12-22, https://alexandre.alapetite.fr/doc-alex/php-http-304/
------------------------------------------------------------------
Written by Alexandre Alapetite in 2004, https://alexandre.alapetite.fr/cv/
@@ -82,8 +82,8 @@ $_sessionMode = false;
function httpConditional(int $UnixTimeStamp, int $cacheSeconds = 0, int $cachePrivacy = 0, bool $feedMode = false, bool $compression = false, bool $session = false): bool {
if (headers_sent()) return false;
- if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName = $_SERVER['SCRIPT_FILENAME'];
- elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName = $_SERVER['PATH_TRANSLATED'];
+ if (is_string($_SERVER['SCRIPT_FILENAME'] ?? null)) $scriptName = $_SERVER['SCRIPT_FILENAME'];
+ elseif (is_string($_SERVER['PATH_TRANSLATED'] ?? null)) $scriptName = $_SERVER['PATH_TRANSLATED'];
else return false;
if ((!$feedMode) && (($modifScript = (int)filemtime($scriptName)) > $UnixTimeStamp))
@@ -98,7 +98,7 @@ function httpConditional(int $UnixTimeStamp, int $cacheSeconds = 0, int $cachePr
$dateCacheClient = 'Thu, 10 Jan 1980 20:30:40 GMT';
//rfc2616-sec14.html#sec14.19 //='"0123456789abcdef0123456789abcdef"'
- if (isset($_SERVER['QUERY_STRING'])) $myQuery = '?' . $_SERVER['QUERY_STRING'];
+ if (is_string($_SERVER['QUERY_STRING'] ?? null)) $myQuery = '?' . $_SERVER['QUERY_STRING'];
else $myQuery = '';
if ($session && isset($_SESSION)) {
global $_sessionMode;
@@ -108,13 +108,13 @@ function httpConditional(int $UnixTimeStamp, int $cacheSeconds = 0, int $cachePr
$etagServer = '"' . md5($scriptName . $myQuery . '#' . $dateLastModif) . '"';
// @phpstan-ignore booleanNot.alwaysTrue
- if ((!$is412) && isset($_SERVER['HTTP_IF_MATCH'])) { //rfc2616-sec14.html#sec14.24
+ if ((!$is412) && is_string($_SERVER['HTTP_IF_MATCH'] ?? null)) { //rfc2616-sec14.html#sec14.24
$etagsClient = stripslashes($_SERVER['HTTP_IF_MATCH']);
$etagsClient = str_ireplace('-gzip', '', $etagsClient);
$is412 = (($etagsClient !== '*') && (strpos($etagsClient, $etagServer) === false));
}
// @phpstan-ignore booleanAnd.leftAlwaysTrue
- if ($is304 && isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { //rfc2616-sec14.html#sec14.25 //rfc1945.txt
+ if ($is304 && is_string($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? null)) { //rfc2616-sec14.html#sec14.25 //rfc1945.txt
$nbCond++;
$dateCacheClient = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
$p = strpos($dateCacheClient, ';');
@@ -122,13 +122,13 @@ function httpConditional(int $UnixTimeStamp, int $cacheSeconds = 0, int $cachePr
$dateCacheClient = substr($dateCacheClient, 0, $p);
$is304 = ($dateCacheClient == $dateLastModif);
}
- if ($is304 && isset($_SERVER['HTTP_IF_NONE_MATCH'])) { //rfc2616-sec14.html#sec14.26
+ if ($is304 && is_string($_SERVER['HTTP_IF_NONE_MATCH'] ?? null)) { //rfc2616-sec14.html#sec14.26
$nbCond++;
$etagClient = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
$etagClient = str_ireplace('-gzip', '', $etagClient);
$is304 = (($etagClient === $etagServer) || ($etagClient === '*'));
}
- if ((!$is412) && isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) { //rfc2616-sec14.html#sec14.28
+ if ((!$is412) && is_string($_SERVER['HTTP_IF_UNMODIFIED_SINCE'] ?? null)) { //rfc2616-sec14.html#sec14.28
$dateCacheClient = $_SERVER['HTTP_IF_UNMODIFIED_SINCE'];
$p = strpos($dateCacheClient, ';');
if ($p !== false)
@@ -200,13 +200,13 @@ function _httpConditionalCallBack(string $buffer, int $mode = 5): string {
function httpConditionalRefresh(int $UnixTimeStamp): void {
if (headers_sent()) return;
- if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName = $_SERVER['SCRIPT_FILENAME'];
- elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName = $_SERVER['PATH_TRANSLATED'];
+ if (is_string($_SERVER['SCRIPT_FILENAME'] ?? null)) $scriptName = $_SERVER['SCRIPT_FILENAME'];
+ elseif (is_string($_SERVER['PATH_TRANSLATED'] ?? null)) $scriptName = $_SERVER['PATH_TRANSLATED'];
else return;
$dateLastModif = gmdate('D, d M Y H:i:s \G\M\T', $UnixTimeStamp);
- if (isset($_SERVER['QUERY_STRING'])) $myQuery = '?' . $_SERVER['QUERY_STRING'];
+ if (is_string($_SERVER['QUERY_STRING'] ?? null)) $myQuery = '?' . $_SERVER['QUERY_STRING'];
else $myQuery = '';
global $_sessionMode;
if ($_sessionMode && isset($_SESSION))
diff --git a/lib/lib_rss.php b/lib/lib_rss.php
index 8eb89c578..c93243eff 100644
--- a/lib/lib_rss.php
+++ b/lib/lib_rss.php
@@ -85,6 +85,32 @@ spl_autoload_register('classAutoloader');
//</Auto-loading>
/**
+ * @param array<mixed,mixed> $array
+ * @phpstan-assert-if-true array<string,mixed> $array
+ */
+function is_array_keys_string(array $array): bool {
+ foreach ($array as $key => $value) {
+ if (!is_string($key)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * @param array<mixed,mixed> $array
+ * @phpstan-assert-if-true array<mixed,string> $array
+ */
+function is_array_values_string(array $array): bool {
+ foreach ($array as $value) {
+ if (!is_string($value)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
* Memory efficient replacement of `echo json_encode(...)`
* @param array<mixed>|mixed $json
* @param int $optimisationDepth Number of levels for which to perform memory optimisation
@@ -231,6 +257,7 @@ function timestamptodate(int $t, bool $hour = true): string {
* Decode HTML entities but preserve XML entities.
*/
function html_only_entity_decode(?string $text): string {
+ /** @var array<string,string>|null $htmlEntitiesOnly */
static $htmlEntitiesOnly = null;
if ($htmlEntitiesOnly === null) {
$htmlEntitiesOnly = array_flip(array_diff(
@@ -252,7 +279,7 @@ function sensitive_log($log): array|string {
foreach ($log as $k => $v) {
if (in_array($k, ['api_key', 'Passwd', 'T'], true)) {
$log[$k] = '██';
- } elseif (is_array($v) || is_string($v)) {
+ } elseif ((is_array($v) && is_array_keys_string($v)) || is_string($v)) {
$log[$k] = sensitive_log($v);
} else {
return '';
@@ -298,7 +325,9 @@ function customSimplePie(array $attributes = [], array $curl_options = []): \Sim
}
if (!empty($attributes['curl_params']) && is_array($attributes['curl_params'])) {
foreach ($attributes['curl_params'] as $co => $v) {
- $curl_options[$co] = $v;
+ if (is_int($co)) {
+ $curl_options[$co] = $v;
+ }
}
}
$simplePie->set_curl_options($curl_options);
@@ -366,13 +395,18 @@ function sanitizeHTML(string $data, string $base = '', ?int $maxLength = null):
if ($maxLength !== null) {
$data = mb_strcut($data, 0, $maxLength, 'UTF-8');
}
+ /** @var \SimplePie\SimplePie|null $simplePie */
static $simplePie = null;
- if ($simplePie == null) {
+ if ($simplePie === null) {
$simplePie = customSimplePie();
$simplePie->enable_cache(false);
$simplePie->init();
}
- $result = html_only_entity_decode($simplePie->sanitize->sanitize($data, \SimplePie\SimplePie::CONSTRUCT_HTML, $base));
+ $sanitized = $simplePie->sanitize->sanitize($data, \SimplePie\SimplePie::CONSTRUCT_HTML, $base);
+ if (!is_string($sanitized)) {
+ return '';
+ }
+ $result = html_only_entity_decode($sanitized);
if ($maxLength !== null && strlen($result) > $maxLength) {
//Sanitizing has made the result too long so try again shorter
$data = mb_strcut($result, 0, (2 * $maxLength) - strlen($result) - 2, 'UTF-8');
@@ -504,6 +538,9 @@ function httpGet(string $url, string $cachePath, string $type = 'html', array $a
// TODO: Implement HTTP 1.1 conditional GET If-Modified-Since
$ch = curl_init();
+ if ($ch === false) {
+ return '';
+ }
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => ['Accept: ' . $accept],
@@ -592,9 +629,10 @@ function lazyimg(string $content): string {
/** @return numeric-string */
function uTimeString(): string {
$t = @gettimeofday();
- $result = $t['sec'] . str_pad('' . $t['usec'], 6, '0', STR_PAD_LEFT);
- /** @var numeric-string @result */
- return $result;
+ $sec = is_numeric($t['sec']) ? (int)$t['sec'] : 0;
+ $usec = is_numeric($t['usec']) ? (int)$t['usec'] : 0;
+ $result = ((string)$sec) . str_pad((string)$usec, 6, '0', STR_PAD_LEFT);
+ return ctype_digit($result) ? $result : '0';
}
function invalidateHttpCache(string $username = ''): bool {
@@ -606,7 +644,7 @@ function invalidateHttpCache(string $username = ''): bool {
}
/**
- * @return array<string>
+ * @return list<string>
*/
function listUsers(): array {
$final_list = [];
@@ -712,9 +750,9 @@ function checkCIDR(string $ip, string $range): bool {
* Use CONN_REMOTE_ADDR (if available, to be robust even when using Apache mod_remoteip) or REMOTE_ADDR environment variable to determine the connection IP.
*/
function connectionRemoteAddress(): string {
- $remoteIp = $_SERVER['CONN_REMOTE_ADDR'] ?? '';
+ $remoteIp = is_string($_SERVER['CONN_REMOTE_ADDR'] ?? null) ? $_SERVER['CONN_REMOTE_ADDR'] : '';
if ($remoteIp == '') {
- $remoteIp = $_SERVER['REMOTE_ADDR'] ?? '';
+ $remoteIp = is_string($_SERVER['REMOTE_ADDR'] ?? null) ? $_SERVER['REMOTE_ADDR'] : '';
}
if ($remoteIp == 0) {
$remoteIp = '';
@@ -752,17 +790,17 @@ function checkTrustedIP(): bool {
}
function httpAuthUser(bool $onlyTrusted = true): string {
- if (!empty($_SERVER['REMOTE_USER'])) {
+ if (!empty($_SERVER['REMOTE_USER']) && is_string($_SERVER['REMOTE_USER'])) {
return $_SERVER['REMOTE_USER'];
}
- if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) {
+ if (!empty($_SERVER['REDIRECT_REMOTE_USER']) && is_string($_SERVER['REDIRECT_REMOTE_USER'])) {
return $_SERVER['REDIRECT_REMOTE_USER'];
}
if (!$onlyTrusted || checkTrustedIP()) {
- if (!empty($_SERVER['HTTP_REMOTE_USER'])) {
+ if (!empty($_SERVER['HTTP_REMOTE_USER']) && is_string($_SERVER['HTTP_REMOTE_USER'])) {
return $_SERVER['HTTP_REMOTE_USER'];
}
- if (!empty($_SERVER['HTTP_X_WEBAUTH_USER'])) {
+ if (!empty($_SERVER['HTTP_X_WEBAUTH_USER']) && is_string($_SERVER['HTTP_X_WEBAUTH_USER'])) {
return $_SERVER['HTTP_X_WEBAUTH_USER'];
}
}
@@ -872,14 +910,14 @@ function recursive_unlink(string $dir): bool {
/**
* Remove queries where $get is appearing.
* @param string $get the get attribute which should be removed.
- * @param array<int,array<string,string|int>> $queries an array of queries.
- * @return array<int,array<string,string|int>> without queries where $get is appearing.
+ * @param array<int,array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string}> $queries an array of queries.
+ * @return array<int,array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string}> without queries where $get is appearing.
*/
function remove_query_by_get(string $get, array $queries): array {
$final_queries = [];
- foreach ($queries as $key => $query) {
+ foreach ($queries as $query) {
if (empty($query['get']) || $query['get'] !== $get) {
- $final_queries[$key] = $query;
+ $final_queries[] = $query;
}
}
return $final_queries;
@@ -901,7 +939,7 @@ const SHORTCUT_KEYS = [
/**
* @param array<string> $shortcuts
- * @return array<string>
+ * @return list<string>
*/
function getNonStandardShortcuts(array $shortcuts): array {
$standard = strtolower(implode(' ', SHORTCUT_KEYS));
@@ -911,7 +949,7 @@ function getNonStandardShortcuts(array $shortcuts): array {
return $shortcut !== '' && stripos($standard, $shortcut) === false;
});
- return $nonStandard;
+ return array_values($nonStandard);
}
function errorMessageInfo(string $errorTitle, string $error = ''): string {