aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/AttributesTrait.php60
-rw-r--r--app/Models/Auth.php38
-rw-r--r--app/Models/BooleanSearch.php12
-rw-r--r--app/Models/Category.php4
-rw-r--r--app/Models/CategoryDAO.php25
-rw-r--r--app/Models/Context.php82
-rw-r--r--app/Models/DatabaseDAO.php4
-rw-r--r--app/Models/DatabaseDAOPGSQL.php4
-rw-r--r--app/Models/DatabaseDAOSQLite.php6
-rw-r--r--app/Models/Entry.php53
-rw-r--r--app/Models/EntryDAO.php84
-rw-r--r--app/Models/EntryDAOSQLite.php2
-rw-r--r--app/Models/Factory.php12
-rw-r--r--app/Models/Feed.php74
-rw-r--r--app/Models/FeedDAO.php16
-rw-r--r--app/Models/FilterAction.php2
-rw-r--r--app/Models/FilterActionsTrait.php18
-rw-r--r--app/Models/FormAuth.php8
-rw-r--r--app/Models/StatsDAO.php2
-rw-r--r--app/Models/SystemConfiguration.php5
-rw-r--r--app/Models/TagDAO.php6
-rw-r--r--app/Models/Themes.php5
-rw-r--r--app/Models/UserConfiguration.php42
23 files changed, 339 insertions, 225 deletions
diff --git a/app/Models/AttributesTrait.php b/app/Models/AttributesTrait.php
index 39154182b..e94a973d9 100644
--- a/app/Models/AttributesTrait.php
+++ b/app/Models/AttributesTrait.php
@@ -10,28 +10,54 @@ trait FreshRSS_AttributesTrait {
*/
private array $attributes = [];
+ /** @return array<string,mixed> */
+ public function attributes(): array {
+ return $this->attributes;
+ }
+
/**
- * @phpstan-return ($key is non-empty-string ? mixed : array<string,mixed>)
- * @return array<string,mixed>|mixed|null
+ * @param non-empty-string $key
+ * @return array<int|string,mixed>|null
*/
- public function attributes(string $key = '') {
- if ($key === '') {
- return $this->attributes;
- } else {
- return $this->attributes[$key] ?? null;
+ public function attributeArray(string $key): ?array {
+ $a = $this->attributes[$key] ?? null;
+ return is_array($a) ? $a : null;
+ }
+
+ /** @param non-empty-string $key */
+ public function attributeBoolean(string $key): ?bool {
+ $a = $this->attributes[$key] ?? null;
+ return is_bool($a) ? $a : null;
+ }
+
+ /** @param non-empty-string $key */
+ public function attributeInt(string $key): ?int {
+ $a = $this->attributes[$key] ?? null;
+ return is_int($a) ? $a : null;
+ }
+
+ /** @param non-empty-string $key */
+ public function attributeString(string $key): ?string {
+ $a = $this->attributes[$key] ?? null;
+ return is_string($a) ? $a : null;
+ }
+
+ /** @param string|array<string,mixed> $values Values, not HTML-encoded */
+ public function _attributes($values): void {
+ if (is_string($values)) {
+ $values = json_decode($values, true);
+ }
+ if (is_array($values)) {
+ $this->attributes = $values;
}
}
- /** @param string|array<mixed>|bool|int|null $value Value, not HTML-encoded */
- public function _attributes(string $key, $value = null): void {
- if ($key == '') {
- if (is_string($value)) {
- $value = json_decode($value, true);
- }
- if (is_array($value)) {
- $this->attributes = $value;
- }
- } elseif ($value === null) {
+ /**
+ * @param non-empty-string $key
+ * @param array<string,mixed>|mixed|null $value Value, not HTML-encoded
+ */
+ public function _attribute(string $key, $value = null): void {
+ if ($value === null) {
unset($this->attributes[$key]);
} else {
$this->attributes[$key] = $value;
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index e5f7fc0b9..c66bb5016 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -24,7 +24,7 @@ class FreshRSS_Auth {
self::$login_ok = Minz_Session::paramBoolean('loginOk');
$current_user = Minz_User::name();
if ($current_user === null) {
- $current_user = FreshRSS_Context::$system_conf->default_user;
+ $current_user = FreshRSS_Context::systemConf()->default_user;
Minz_Session::_params([
Minz_User::CURRENT_USER => $current_user,
'csrf' => false,
@@ -51,7 +51,7 @@ class FreshRSS_Auth {
* @return bool true if user can be connected, false otherwise.
*/
private static function accessControl(): bool {
- $auth_type = FreshRSS_Context::$system_conf->auth_type;
+ $auth_type = FreshRSS_Context::systemConf()->auth_type;
switch ($auth_type) {
case 'form':
$credentials = FreshRSS_FormAuth::getCredentialsFromCookie();
@@ -71,13 +71,13 @@ class FreshRSS_Auth {
return false;
}
$login_ok = FreshRSS_UserDAO::exists($current_user);
- if (!$login_ok && FreshRSS_Context::$system_conf->http_auth_auto_register) {
+ if (!$login_ok && FreshRSS_Context::systemConf()->http_auth_auto_register) {
$email = null;
- if (FreshRSS_Context::$system_conf->http_auth_auto_register_email_field !== '' &&
- isset($_SERVER[FreshRSS_Context::$system_conf->http_auth_auto_register_email_field])) {
- $email = (string)$_SERVER[FreshRSS_Context::$system_conf->http_auth_auto_register_email_field];
+ if (FreshRSS_Context::systemConf()->http_auth_auto_register_email_field !== '' &&
+ isset($_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field])) {
+ $email = (string)$_SERVER[FreshRSS_Context::systemConf()->http_auth_auto_register_email_field];
}
- $language = Minz_Translate::getLanguage(null, Minz_Request::getPreferredLanguages(), FreshRSS_Context::$system_conf->language);
+ $language = Minz_Translate::getLanguage(null, Minz_Request::getPreferredLanguages(), FreshRSS_Context::systemConf()->language);
Minz_Translate::init($language);
$login_ok = FreshRSS_user_Controller::createUser($current_user, $email, '', [
'language' => $language,
@@ -103,17 +103,17 @@ class FreshRSS_Auth {
*/
public static function giveAccess(): bool {
FreshRSS_Context::initUser();
- if (FreshRSS_Context::$user_conf == null) {
+ if (!FreshRSS_Context::hasUserConf()) {
self::$login_ok = false;
return false;
}
- switch (FreshRSS_Context::$system_conf->auth_type) {
+ switch (FreshRSS_Context::systemConf()->auth_type) {
case 'form':
- self::$login_ok = Minz_Session::paramString('passwordHash') === FreshRSS_Context::$user_conf->passwordHash;
+ self::$login_ok = Minz_Session::paramString('passwordHash') === FreshRSS_Context::userConf()->passwordHash;
break;
case 'http_auth':
- $current_user = Minz_User::name();
+ $current_user = Minz_User::name() ?? '';
self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0;
break;
case 'none':
@@ -138,12 +138,12 @@ class FreshRSS_Auth {
* @return bool true if user has corresponding access, false else.
*/
public static function hasAccess(string $scope = 'general'): bool {
- if (FreshRSS_Context::$user_conf == null) {
+ if (!FreshRSS_Context::hasUserConf()) {
return false;
}
$currentUser = Minz_User::name();
- $isAdmin = FreshRSS_Context::$user_conf->is_admin;
- $default_user = FreshRSS_Context::$system_conf->default_user;
+ $isAdmin = FreshRSS_Context::userConf()->is_admin;
+ $default_user = FreshRSS_Context::systemConf()->default_user;
$ok = self::$login_ok;
switch ($scope) {
case 'general':
@@ -180,11 +180,11 @@ class FreshRSS_Auth {
}
}
if ($username == '') {
- $username = FreshRSS_Context::$system_conf->default_user;
+ $username = FreshRSS_Context::systemConf()->default_user;
}
Minz_User::change($username);
- switch (FreshRSS_Context::$system_conf->auth_type) {
+ switch (FreshRSS_Context::systemConf()->auth_type) {
case 'form':
Minz_Session::_param('passwordHash');
FreshRSS_FormAuth::deleteCookie();
@@ -202,20 +202,20 @@ class FreshRSS_Auth {
* Return if authentication is enabled on this instance of FRSS.
*/
public static function accessNeedsLogin(): bool {
- return FreshRSS_Context::$system_conf->auth_type !== 'none';
+ return FreshRSS_Context::systemConf()->auth_type !== 'none';
}
/**
* Return if authentication requires a PHP action.
*/
public static function accessNeedsAction(): bool {
- return FreshRSS_Context::$system_conf->auth_type === 'form';
+ return FreshRSS_Context::systemConf()->auth_type === 'form';
}
public static function csrfToken(): string {
$csrf = Minz_Session::paramString('csrf');
if ($csrf == '') {
- $salt = FreshRSS_Context::$system_conf->salt;
+ $salt = FreshRSS_Context::systemConf()->salt;
$csrf = sha1($salt . uniqid('' . random_int(0, mt_getrandmax()), true));
Minz_Session::_param('csrf', $csrf);
}
diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php
index 8a750a713..78b7593b2 100644
--- a/app/Models/BooleanSearch.php
+++ b/app/Models/BooleanSearch.php
@@ -19,14 +19,20 @@ class FreshRSS_BooleanSearch {
public function __construct(string $input, int $level = 0, string $operator = 'AND') {
$this->operator = $operator;
$input = trim($input);
- if ($input == '') {
+ if ($input === '') {
return;
}
$this->raw_input = $input;
if ($level === 0) {
$input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input);
+ if (!is_string($input)) {
+ return;
+ }
$input = preg_replace('/(?<=[\s!-]|^)&quot;(.*?)&quot;/', '"\1"', $input);
+ if (!is_string($input)) {
+ return;
+ }
$input = $this->parseUserQueryNames($input);
$input = $this->parseUserQueryIds($input);
@@ -53,7 +59,7 @@ class FreshRSS_BooleanSearch {
if (!empty($all_matches)) {
/** @var array<string,FreshRSS_UserQuery> */
$queries = [];
- foreach (FreshRSS_Context::$user_conf->queries as $raw_query) {
+ foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
$query = new FreshRSS_UserQuery($raw_query);
$queries[$query->getName()] = $query;
}
@@ -95,7 +101,7 @@ class FreshRSS_BooleanSearch {
/** @var array<string,FreshRSS_UserQuery> */
$queries = [];
- foreach (FreshRSS_Context::$user_conf->queries as $raw_query) {
+ foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
$query = new FreshRSS_UserQuery($raw_query, $feed_dao, $category_dao, $tag_dao);
$queries[] = $query;
}
diff --git a/app/Models/Category.php b/app/Models/Category.php
index b1e35650a..cc25a1ec0 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -169,8 +169,8 @@ class FreshRSS_Category extends Minz_Model {
}
public function refreshDynamicOpml(): bool {
- $url = $this->attributes('opml_url');
- if ($url == '') {
+ $url = $this->attributeString('opml_url');
+ if ($url == null) {
return false;
}
$ok = true;
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 20347e4f2..417ff7a6c 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -30,8 +30,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
} elseif ('attributes' === $name) { //v1.15.0
$ok = $this->pdo->exec('ALTER TABLE `_category` ADD COLUMN attributes TEXT') !== false;
- /** @var array<array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
- * 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'ttl':int,'attributes':string}> $feeds */
+ /** @var array<array{'id':int,'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
+ * 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'keep_history':?int,'ttl':int,'attributes':string}> $feeds */
$feeds = $this->fetchAssoc('SELECT * FROM `_feed`') ?? [];
$stm = $this->pdo->prepare('UPDATE `_feed` SET attributes = :attributes WHERE id = :id');
@@ -153,7 +153,7 @@ SQL;
}
/**
- * @param array{'name':string,'kind':int,'attributes'?:string|array<string,mixed>} $valuesTmp
+ * @param array{'name':string,'kind':int,'attributes'?:array<string,mixed>|mixed|null} $valuesTmp
* @return int|false
* @throws JsonException
*/
@@ -230,6 +230,7 @@ SQL;
$stm = $this->pdo->query($sql);
if ($stm !== false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':int,'name':string,'kind':int,'lastUpdate':int,'error':int,'attributes'?:array<string,mixed>} $row */
yield $row;
}
} else {
@@ -245,7 +246,7 @@ SQL;
public function searchById(int $id): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE id=:id';
$res = $this->fetchAssoc($sql, ['id' => $id]) ?? [];
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
+ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
$cat = self::daoToCategory($res);
return $cat[0] ?? null;
}
@@ -253,7 +254,7 @@ SQL;
public function searchByName(string $name): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE name=:name';
$res = $this->fetchAssoc($sql, ['name' => $name]) ?? [];
- /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
+ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
$cat = self::daoToCategory($res);
return $cat[0] ?? null;
}
@@ -263,8 +264,8 @@ SQL;
$categories = $this->listCategories($prePopulateFeeds, $details);
uasort($categories, static function (FreshRSS_Category $a, FreshRSS_Category $b) {
- $aPosition = $a->attributes('position');
- $bPosition = $b->attributes('position');
+ $aPosition = $a->attributeInt('position');
+ $bPosition = $b->attributeInt('position');
if ($aPosition === $bPosition) {
return ($a->name() < $b->name()) ? -1 : 1;
} elseif (null === $aPosition) {
@@ -332,9 +333,9 @@ SQL;
public function getDefault(): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE id=:id';
- $res = $this->fetchAssoc($sql, [':id' => self::DEFAULTCATEGORYID]);
+ $res = $this->fetchAssoc($sql, [':id' => self::DEFAULTCATEGORYID]) ?? [];
/** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
- $cat = self::daoToCategory($res ?? []);
+ $cat = self::daoToCategory($res);
if (isset($cat[0])) {
return $cat[0];
} else {
@@ -444,7 +445,7 @@ SQL;
$feedDao::daoToFeed($feedsDao, $previousLine['c_id'])
);
$cat->_kind($previousLine['c_kind']);
- $cat->_attributes('', $previousLine['c_attributes'] ?? '[]');
+ $cat->_attributes($previousLine['c_attributes'] ?? '[]');
$list[(int)$previousLine['c_id']] = $cat;
$feedsDao = []; //Prepare for next category
@@ -464,7 +465,7 @@ SQL;
$cat->_kind($previousLine['c_kind']);
$cat->_lastUpdate($previousLine['c_last_update'] ?? 0);
$cat->_error($previousLine['c_error'] ?? 0);
- $cat->_attributes('', $previousLine['c_attributes'] ?? []);
+ $cat->_attributes($previousLine['c_attributes'] ?? []);
$list[(int)$previousLine['c_id']] = $cat;
}
@@ -487,7 +488,7 @@ SQL;
$cat->_kind($dao['kind']);
$cat->_lastUpdate($dao['lastUpdate'] ?? 0);
$cat->_error($dao['error'] ?? 0);
- $cat->_attributes('', $dao['attributes'] ?? '');
+ $cat->_attributes($dao['attributes'] ?? '');
$list[] = $cat;
}
diff --git a/app/Models/Context.php b/app/Models/Context.php
index ac5547aa1..3ea5a29eb 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -7,8 +7,6 @@ declare(strict_types=1);
*/
final class FreshRSS_Context {
- public static ?FreshRSS_UserConfiguration $user_conf = null;
- public static ?FreshRSS_SystemConfiguration $system_conf = null;
/**
* @var array<int,FreshRSS_Category>
*/
@@ -57,21 +55,42 @@ final class FreshRSS_Context {
public static bool $isCli = false;
/**
+ * @deprecated Will be made `private`; use `FreshRSS_Context::systemConf()` instead.
+ * @internal
+ */
+ public static ?FreshRSS_SystemConfiguration $system_conf = null;
+ /**
+ * @deprecated Will be made `private`; use `FreshRSS_Context::userConf()` instead.
+ * @internal
+ */
+ public static ?FreshRSS_UserConfiguration $user_conf = null;
+
+ /**
* Initialize the context for the global system.
*/
- public static function initSystem(bool $reload = false): FreshRSS_SystemConfiguration {
- if ($reload || FreshRSS_Context::$system_conf == null) {
+ public static function initSystem(bool $reload = false): void {
+ if ($reload || FreshRSS_Context::$system_conf === null) {
//TODO: Keep in session what we need instead of always reloading from disk
FreshRSS_Context::$system_conf = FreshRSS_SystemConfiguration::init(DATA_PATH . '/config.php', FRESHRSS_PATH . '/config.default.php');
}
+ }
+
+ public static function &systemConf(): FreshRSS_SystemConfiguration {
+ if (FreshRSS_Context::$system_conf === null) {
+ throw new FreshRSS_Context_Exception('System configuration not initialised!');
+ }
return FreshRSS_Context::$system_conf;
}
+ public static function hasSystemConf(): bool {
+ return FreshRSS_Context::$system_conf !== null;
+ }
+
/**
* Initialize the context for the current user.
* @throws Minz_ConfigurationParamException
*/
- public static function initUser(string $username = '', bool $userMustExist = true): ?FreshRSS_UserConfiguration {
+ public static function initUser(string $username = '', bool $userMustExist = true): void {
FreshRSS_Context::$user_conf = null;
if (!isset($_SESSION)) {
Minz_Session::init('FreshRSS');
@@ -103,14 +122,16 @@ final class FreshRSS_Context {
Minz_Session::unlock();
if (FreshRSS_Context::$user_conf == null) {
- return null;
+ return;
}
FreshRSS_Context::$search = new FreshRSS_BooleanSearch('');
//Legacy
- $oldEntries = (int)FreshRSS_Context::$user_conf->param('old_entries', 0);
- $keepMin = (int)FreshRSS_Context::$user_conf->param('keep_history_default', -5);
+ $oldEntries = FreshRSS_Context::$user_conf->param('old_entries', 0);
+ $oldEntries = is_numeric($oldEntries) ? (int)$oldEntries : 0;
+ $keepMin = FreshRSS_Context::$user_conf->param('keep_history_default', -5);
+ $keepMin = is_numeric($keepMin) ? (int)$keepMin : -5;
if ($oldEntries > 0 || $keepMin > -5) { //Freshrss < 1.15
$archiving = FreshRSS_Context::$user_conf->archiving;
$archiving['keep_max'] = false;
@@ -130,10 +151,23 @@ final class FreshRSS_Context {
if (!in_array(FreshRSS_Context::$user_conf->display_categories, [ 'active', 'remember', 'all', 'none' ], true)) {
FreshRSS_Context::$user_conf->display_categories = FreshRSS_Context::$user_conf->display_categories === true ? 'all' : 'active';
}
+ }
+ public static function &userConf(): FreshRSS_UserConfiguration {
+ if (FreshRSS_Context::$user_conf === null) {
+ throw new FreshRSS_Context_Exception('User configuration not initialised!');
+ }
return FreshRSS_Context::$user_conf;
}
+ public static function hasUserConf(): bool {
+ return FreshRSS_Context::$user_conf !== null;
+ }
+
+ public static function clearUserConf(): void {
+ FreshRSS_Context::$user_conf = null;
+ }
+
/**
* This action updates the Context object by using request parameters.
*
@@ -162,28 +196,28 @@ final class FreshRSS_Context {
self::_get(Minz_Request::paramString('get') ?: 'a');
- self::$state = Minz_Request::paramInt('state') ?: self::$user_conf->default_state;
+ self::$state = Minz_Request::paramInt('state') ?: FreshRSS_Context::userConf()->default_state;
$state_forced_by_user = Minz_Request::paramString('state') !== '';
if (!$state_forced_by_user && !self::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
- if (self::$user_conf->default_view === 'all') {
+ if (FreshRSS_Context::userConf()->default_view === 'all') {
self::$state |= FreshRSS_Entry::STATE_ALL;
- } elseif (self::$user_conf->default_view === 'adaptive' && self::$get_unread <= 0) {
+ } elseif (FreshRSS_Context::userConf()->default_view === 'adaptive' && self::$get_unread <= 0) {
self::$state |= FreshRSS_Entry::STATE_READ;
}
- if (self::$user_conf->show_fav_unread &&
+ if (FreshRSS_Context::userConf()->show_fav_unread &&
(self::isCurrentGet('s') || self::isCurrentGet('T') || self::isTag())) {
self::$state |= FreshRSS_Entry::STATE_READ;
}
}
self::$search = new FreshRSS_BooleanSearch(Minz_Request::paramString('search'));
- $order = Minz_Request::paramString('order') ?: self::$user_conf->sort_order;
+ $order = Minz_Request::paramString('order') ?: FreshRSS_Context::userConf()->sort_order;
self::$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
- self::$number = Minz_Request::paramInt('nb') ?: self::$user_conf->posts_per_page;
- if (self::$number > self::$user_conf->max_posts_per_rss) {
+ self::$number = Minz_Request::paramInt('nb') ?: FreshRSS_Context::userConf()->posts_per_page;
+ if (self::$number > FreshRSS_Context::userConf()->max_posts_per_rss) {
self::$number = max(
- self::$user_conf->max_posts_per_rss,
- self::$user_conf->posts_per_page);
+ FreshRSS_Context::userConf()->max_posts_per_rss,
+ FreshRSS_Context::userConf()->posts_per_page);
}
self::$first_id = Minz_Request::paramString('next');
self::$sinceHours = Minz_Request::paramInt('hours');
@@ -335,19 +369,19 @@ final class FreshRSS_Context {
case 'a':
self::$current_get['all'] = true;
self::$name = _t('index.feed.title');
- self::$description = self::$system_conf->meta_description;
+ self::$description = FreshRSS_Context::systemConf()->meta_description;
self::$get_unread = self::$total_unread;
break;
case 'i':
self::$current_get['important'] = true;
self::$name = _t('index.menu.important');
- self::$description = self::$system_conf->meta_description;
+ self::$description = FreshRSS_Context::systemConf()->meta_description;
self::$get_unread = self::$total_unread;
break;
case 's':
self::$current_get['starred'] = true;
self::$name = _t('index.feed.title_fav');
- self::$description = self::$system_conf->meta_description;
+ self::$description = FreshRSS_Context::systemConf()->meta_description;
self::$get_unread = self::$total_starred['unread'];
// Update state if favorite is not yet enabled.
@@ -355,7 +389,7 @@ final class FreshRSS_Context {
break;
case 'f':
// We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description
- $feed = FreshRSS_Context::$system_conf->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id);
+ $feed = FreshRSS_Context::systemConf()->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id);
if ($feed === null) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($id);
@@ -427,7 +461,7 @@ final class FreshRSS_Context {
self::$categories = $catDAO->listCategories();
}
- if (self::$user_conf->onread_jump_next && strlen($get) > 2) {
+ if (FreshRSS_Context::userConf()->onread_jump_next && strlen($get) > 2) {
$another_unread_id = '';
$found_current_get = false;
switch ($get[0]) {
@@ -491,7 +525,7 @@ final class FreshRSS_Context {
* - the "unread" state is enable
*/
public static function isAutoRemoveAvailable(): bool {
- if (!self::$user_conf->auto_remove_article) {
+ if (!FreshRSS_Context::userConf()->auto_remove_article) {
return false;
}
if (self::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
@@ -510,7 +544,7 @@ final class FreshRSS_Context {
* are read.
*/
public static function isStickyPostEnabled(): bool {
- if (self::$user_conf->sticky_post) {
+ if (FreshRSS_Context::userConf()->sticky_post) {
return true;
}
if (self::isAutoRemoveAvailable()) {
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php
index 7dbe1db3f..cdc74fa12 100644
--- a/app/Models/DatabaseDAO.php
+++ b/app/Models/DatabaseDAO.php
@@ -22,7 +22,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function create(): string {
require_once(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php');
- $db = FreshRSS_Context::$system_conf->db;
+ $db = FreshRSS_Context::systemConf()->db;
try {
$sql = sprintf($GLOBALS['SQL_CREATE_DB'], empty($db['base']) ? '' : $db['base']);
@@ -174,7 +174,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
}
public function size(bool $all = false): int {
- $db = FreshRSS_Context::$system_conf->db;
+ $db = FreshRSS_Context::systemConf()->db;
// MariaDB does not refresh size information automatically
$sql = <<<'SQL'
diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
index 4a92dd184..fe3d6149d 100644
--- a/app/Models/DatabaseDAOPGSQL.php
+++ b/app/Models/DatabaseDAOPGSQL.php
@@ -11,7 +11,7 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
public const UNDEFINED_TABLE = '42P01';
public function tablesAreCorrect(): bool {
- $db = FreshRSS_Context::$system_conf->db;
+ $db = FreshRSS_Context::systemConf()->db;
$sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=:tableowner';
$res = $this->fetchAssoc($sql, [':tableowner' => $db['user']]);
if ($res == null) {
@@ -58,7 +58,7 @@ SQL;
public function size(bool $all = false): int {
if ($all) {
- $db = FreshRSS_Context::$system_conf->db;
+ $db = FreshRSS_Context::systemConf()->db;
$res = $this->fetchColumn('SELECT pg_database_size(:base)', 0, [':base' => $db['base']]);
} else {
$sql = <<<SQL
diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php
index 787380637..e72cc74e8 100644
--- a/app/Models/DatabaseDAOSQLite.php
+++ b/app/Models/DatabaseDAOSQLite.php
@@ -65,12 +65,12 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
$sum = 0;
if ($all) {
foreach (glob(DATA_PATH . '/users/*/db.sqlite') ?: [] as $filename) {
- $sum += @filesize($filename);
+ $sum += (@filesize($filename) ?: 0);
}
} else {
- $sum = @filesize(DATA_PATH . '/users/' . $this->current_user . '/db.sqlite');
+ $sum = (@filesize(DATA_PATH . '/users/' . $this->current_user . '/db.sqlite') ?: 0);
}
- return intval($sum);
+ return $sum;
}
public function optimize(): bool {
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 186b1f166..62ba91db3 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -90,7 +90,7 @@ class FreshRSS_Entry extends Minz_Model {
$entry->_lastSeen($dao['lastSeen']);
}
if (!empty($dao['attributes'])) {
- $entry->_attributes('', $dao['attributes']);
+ $entry->_attributes($dao['attributes']);
}
if (!empty($dao['hash'])) {
$entry->_hash($dao['hash']);
@@ -145,7 +145,7 @@ class FreshRSS_Entry extends Minz_Model {
* Provides the original content without additional content potentially added by loadCompleteContent().
*/
public function originalContent(): string {
- return preg_replace('#<!-- FULLCONTENT start //-->.*<!-- FULLCONTENT end //-->#s', '', $this->content);
+ return preg_replace('#<!-- FULLCONTENT start //-->.*<!-- FULLCONTENT end //-->#s', '', $this->content) ?? '';
}
/**
@@ -160,7 +160,7 @@ class FreshRSS_Entry extends Minz_Model {
$content = $this->content;
- $thumbnailAttribute = $this->attributes('thumbnail');
+ $thumbnailAttribute = $this->attributeArray('thumbnail') ?? [];
if (!empty($thumbnailAttribute['url'])) {
$elink = $thumbnailAttribute['url'];
if ($allowDuplicateEnclosures || !self::containsLink($content, $elink)) {
@@ -174,12 +174,15 @@ HTML;
}
}
- $attributeEnclosures = $this->attributes('enclosures');
+ $attributeEnclosures = $this->attributeArray('enclosures');
if (empty($attributeEnclosures)) {
return $content;
}
foreach ($attributeEnclosures as $enclosure) {
+ if (!is_array($enclosure)) {
+ continue;
+ }
$elink = $enclosure['url'] ?? '';
if ($elink == '') {
continue;
@@ -192,7 +195,10 @@ HTML;
$length = $enclosure['length'] ?? 0;
$medium = $enclosure['medium'] ?? '';
$mime = $enclosure['type'] ?? '';
- $thumbnails = $enclosure['thumbnails'] ?? [];
+ $thumbnails = $enclosure['thumbnails'] ?? null;
+ if (!is_array($thumbnails)) {
+ $thumbnails = [];
+ }
$etitle = $enclosure['title'] ?? '';
$content .= "\n";
@@ -235,7 +241,7 @@ HTML;
/** @return Traversable<array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string,'height'?:int,'width'?:int,'thumbnails'?:array<string>}> */
public function enclosures(bool $searchBodyImages = false): Traversable {
- $attributeEnclosures = $this->attributes('enclosures');
+ $attributeEnclosures = $this->attributeArray('enclosures');
if (is_iterable($attributeEnclosures)) {
// FreshRSS 1.20.1+: The enclosures are saved as attributes
yield from $attributeEnclosures;
@@ -249,7 +255,7 @@ HTML;
$dom->loadHTML('<?xml version="1.0" encoding="UTF-8" ?>' . $this->content, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
$xpath = new DOMXPath($dom);
}
- if ($searchEnclosures) {
+ if ($searchEnclosures && $xpath !== null) {
// Legacy code for database entries < FreshRSS 1.20.1
$enclosures = $xpath->query('//div[@class="enclosure"]/p[@class="enclosure-content"]/*[@src]');
if (!empty($enclosures)) {
@@ -272,7 +278,7 @@ HTML;
}
}
}
- if ($searchBodyImages) {
+ if ($searchBodyImages && $xpath !== null) {
$images = $xpath->query('//img');
if (!empty($images)) {
/** @var DOMElement $img */
@@ -300,7 +306,7 @@ HTML;
* @return array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string,'height'?:int,'width'?:int,'thumbnails'?:array<string>}|null
*/
public function thumbnail(bool $searchEnclosures = true): ?array {
- $thumbnail = $this->attributes('thumbnail');
+ $thumbnail = $this->attributeArray('thumbnail') ?? [];
// First, use the provided thumbnail, if any
if (!empty($thumbnail['url'])) {
return $thumbnail;
@@ -558,7 +564,7 @@ HTML;
$ok &= in_array($this->feedId, $filter->getFeedIds(), true);
}
if ($ok && $filter->getNotFeedIds()) {
- $ok &= !in_array($this->feedId, $filter->getFeedIds(), true);
+ $ok &= !in_array($this->feedId, $filter->getNotFeedIds(), true);
}
if ($ok && $filter->getAuthor()) {
foreach ($filter->getAuthor() as $author) {
@@ -630,14 +636,15 @@ HTML;
return (bool)$ok;
}
- /** @param array<string,bool> $titlesAsRead */
+ /** @param array<string,bool|int> $titlesAsRead */
public function applyFilterActions(array $titlesAsRead = []): void {
- if ($this->feed === null) {
+ $feed = $this->feed;
+ if ($feed === null) {
return;
}
if (!$this->isRead()) {
- if ($this->feed->attributes('read_upon_reception') ||
- ($this->feed->attributes('read_upon_reception') === null && FreshRSS_Context::$user_conf->mark_when['reception'])) {
+ if ($feed->attributeBoolean('read_upon_reception') ||
+ ($feed->attributeBoolean('read_upon_reception') === null && FreshRSS_Context::userConf()->mark_when['reception'])) {
$this->_isRead(true);
Minz_ExtensionManager::callHook('entry_auto_read', $this, 'upon_reception');
}
@@ -647,11 +654,11 @@ HTML;
Minz_ExtensionManager::callHook('entry_auto_read', $this, 'same_title_in_feed');
}
}
- FreshRSS_Context::$user_conf->applyFilterActions($this);
- if ($this->feed->category() !== null) {
- $this->feed->category()->applyFilterActions($this);
+ FreshRSS_Context::userConf()->applyFilterActions($this);
+ if ($feed->category() !== null) {
+ $feed->category()->applyFilterActions($this);
}
- $this->feed->applyFilterActions($this);
+ $feed->applyFilterActions($this);
}
public function isDay(int $day, int $today): bool {
@@ -684,10 +691,9 @@ HTML;
if ($maxRedirs > 0) {
//Follow any HTML redirection
- $metas = $xpath->query('//meta[@content]');
- /** @var array<DOMElement> $metas */
+ $metas = $xpath->query('//meta[@content]') ?: [];
foreach ($metas as $meta) {
- if (strtolower(trim($meta->getAttribute('http-equiv'))) === 'refresh') {
+ if ($meta instanceof DOMElement && strtolower(trim($meta->getAttribute('http-equiv'))) === 'refresh') {
$refresh = preg_replace('/^[0-9.; ]*\s*(url\s*=)?\s*/i', '', trim($meta->getAttribute('content')));
$refresh = SimplePie_Misc::absolutize_url($refresh, $url);
if ($refresh != false && $refresh !== $url) {
@@ -712,6 +718,9 @@ HTML;
if (!empty($attributes['path_entries_filter'])) {
$filterednodes = $xpath->query((new Gt\CssXPath\Translator($attributes['path_entries_filter']))->asXPath(), $node) ?: [];
foreach ($filterednodes as $filterednode) {
+ if ($filterednode->parentNode === null) {
+ continue;
+ }
$filterednode->parentNode->removeChild($filterednode);
}
}
@@ -747,7 +756,7 @@ HTML;
if ('' !== $fullContent) {
$fullContent = "<!-- FULLCONTENT start //-->{$fullContent}<!-- FULLCONTENT end //-->";
$originalContent = $this->originalContent();
- switch ($feed->attributes('content_action')) {
+ switch ($feed->attributeString('content_action')) {
case 'prepend':
$this->content = $fullContent . $originalContent;
break;
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 23ac3c918..232db8521 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -315,7 +315,7 @@ SQL;
$affected = 0;
$idsChunks = array_chunk($ids, FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER);
foreach ($idsChunks as $idsChunk) {
- $affected += $this->markFavorite($idsChunk, $is_favorite);
+ $affected += ($this->markFavorite($idsChunk, $is_favorite) ?: 0);
}
return $affected;
}
@@ -387,7 +387,7 @@ SQL;
if (count($ids) < 6) { //Speed heuristics
$affected = 0;
foreach ($ids as $id) {
- $affected += $this->markRead($id, $is_read);
+ $affected += ($this->markRead($id, $is_read) ?: 0);
}
return $affected;
} elseif (count($ids) > FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER) {
@@ -395,7 +395,7 @@ SQL;
$affected = 0;
$idsChunks = array_chunk($ids, FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER);
foreach ($idsChunks as $idsChunk) {
- $affected += $this->markRead($idsChunk, $is_read);
+ $affected += ($this->markRead($idsChunk, $is_read) ?: 0);
}
return $affected;
}
@@ -630,7 +630,7 @@ SQL;
/**
* Remember to call updateCachedValue($id_feed) or updateCachedValues() just after.
- * @param array<string,int|bool|string> $options
+ * @param array<string,bool|int|string> $options
* @return int|false
*/
public function cleanOldEntries(int $id_feed, array $options = []) {
@@ -704,6 +704,8 @@ SQL;
$stm = $this->pdo->query($sql);
if ($stm != false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,
+ * 'hash':string,'is_read':?bool,'is_favorite':?bool,'id_feed':int,'tags':string,'attributes':array<string,mixed>} $row */
yield $row;
}
} else {
@@ -777,7 +779,7 @@ SQL;
}
// Searches are combined by OR and are not recursive
$sub_search = '';
- if ($filter->getEntryIds()) {
+ if ($filter->getEntryIds() !== null) {
$sub_search .= 'AND ' . $alias . 'id IN (';
foreach ($filter->getEntryIds() as $entry_id) {
$sub_search .= '?,';
@@ -786,7 +788,7 @@ SQL;
$sub_search = rtrim($sub_search, ',');
$sub_search .= ') ';
}
- if ($filter->getNotEntryIds()) {
+ if ($filter->getNotEntryIds() !== null) {
$sub_search .= 'AND ' . $alias . 'id NOT IN (';
foreach ($filter->getNotEntryIds() as $entry_id) {
$sub_search .= '?,';
@@ -796,56 +798,56 @@ SQL;
$sub_search .= ') ';
}
- if ($filter->getMinDate()) {
+ if ($filter->getMinDate() !== null) {
$sub_search .= 'AND ' . $alias . 'id >= ? ';
$values[] = "{$filter->getMinDate()}000000";
}
- if ($filter->getMaxDate()) {
+ if ($filter->getMaxDate() !== null) {
$sub_search .= 'AND ' . $alias . 'id <= ? ';
$values[] = "{$filter->getMaxDate()}000000";
}
- if ($filter->getMinPubdate()) {
+ if ($filter->getMinPubdate() !== null) {
$sub_search .= 'AND ' . $alias . 'date >= ? ';
$values[] = $filter->getMinPubdate();
}
- if ($filter->getMaxPubdate()) {
+ if ($filter->getMaxPubdate() !== null) {
$sub_search .= 'AND ' . $alias . 'date <= ? ';
$values[] = $filter->getMaxPubdate();
}
//Negation of date intervals must be combined by OR
- if ($filter->getNotMinDate() || $filter->getNotMaxDate()) {
+ if ($filter->getNotMinDate() !== null || $filter->getNotMaxDate() !== null) {
$sub_search .= 'AND (';
- if ($filter->getNotMinDate()) {
+ if ($filter->getNotMinDate() !== null) {
$sub_search .= $alias . 'id < ?';
$values[] = "{$filter->getNotMinDate()}000000";
if ($filter->getNotMaxDate()) {
$sub_search .= ' OR ';
}
}
- if ($filter->getNotMaxDate()) {
+ if ($filter->getNotMaxDate() !== null) {
$sub_search .= $alias . 'id > ?';
$values[] = "{$filter->getNotMaxDate()}000000";
}
$sub_search .= ') ';
}
- if ($filter->getNotMinPubdate() || $filter->getNotMaxPubdate()) {
+ if ($filter->getNotMinPubdate() !== null || $filter->getNotMaxPubdate() !== null) {
$sub_search .= 'AND (';
- if ($filter->getNotMinPubdate()) {
+ if ($filter->getNotMinPubdate() !== null) {
$sub_search .= $alias . 'date < ?';
$values[] = $filter->getNotMinPubdate();
if ($filter->getNotMaxPubdate()) {
$sub_search .= ' OR ';
}
}
- if ($filter->getNotMaxPubdate()) {
+ if ($filter->getNotMaxPubdate() !== null) {
$sub_search .= $alias . 'date > ?';
$values[] = $filter->getNotMaxPubdate();
}
$sub_search .= ') ';
}
- if ($filter->getFeedIds()) {
+ if ($filter->getFeedIds() !== null) {
$sub_search .= 'AND ' . $alias . 'id_feed IN (';
foreach ($filter->getFeedIds() as $feed_id) {
$sub_search .= '?,';
@@ -854,7 +856,7 @@ SQL;
$sub_search = rtrim($sub_search, ',');
$sub_search .= ') ';
}
- if ($filter->getNotFeedIds()) {
+ if ($filter->getNotFeedIds() !== null) {
$sub_search .= 'AND ' . $alias . 'id_feed NOT IN (';
foreach ($filter->getNotFeedIds() as $feed_id) {
$sub_search .= '?,';
@@ -864,7 +866,7 @@ SQL;
$sub_search .= ') ';
}
- if ($filter->getLabelIds()) {
+ if ($filter->getLabelIds() !== null) {
if ($filter->getLabelIds() === '*') {
$sub_search .= 'AND EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) ';
} else {
@@ -877,7 +879,7 @@ SQL;
$sub_search .= ')) ';
}
}
- if ($filter->getNotLabelIds()) {
+ if ($filter->getNotLabelIds() !== null) {
if ($filter->getNotLabelIds() === '*') {
$sub_search .= 'AND NOT EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) ';
} else {
@@ -891,7 +893,7 @@ SQL;
}
}
- if ($filter->getLabelNames()) {
+ if ($filter->getLabelNames() !== null) {
$sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN (';
foreach ($filter->getLabelNames() as $label_name) {
$sub_search .= '?,';
@@ -900,7 +902,7 @@ SQL;
$sub_search = rtrim($sub_search, ',');
$sub_search .= ')) ';
}
- if ($filter->getNotLabelNames()) {
+ if ($filter->getNotLabelNames() !== null) {
$sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN (';
foreach ($filter->getNotLabelNames() as $label_name) {
$sub_search .= '?,';
@@ -910,57 +912,57 @@ SQL;
$sub_search .= ')) ';
}
- if ($filter->getAuthor()) {
+ if ($filter->getAuthor() !== null) {
foreach ($filter->getAuthor() as $author) {
$sub_search .= 'AND ' . $alias . 'author LIKE ? ';
$values[] = "%{$author}%";
}
}
- if ($filter->getIntitle()) {
+ if ($filter->getIntitle() !== null) {
foreach ($filter->getIntitle() as $title) {
$sub_search .= 'AND ' . $alias . 'title LIKE ? ';
$values[] = "%{$title}%";
}
}
- if ($filter->getTags()) {
+ if ($filter->getTags() !== null) {
foreach ($filter->getTags() as $tag) {
$sub_search .= 'AND ' . static::sqlConcat('TRIM(' . $alias . 'tags) ', " ' #'") . ' LIKE ? ';
$values[] = "%{$tag} #%";
}
}
- if ($filter->getInurl()) {
+ if ($filter->getInurl() !== null) {
foreach ($filter->getInurl() as $url) {
$sub_search .= 'AND ' . $alias . 'link LIKE ? ';
$values[] = "%{$url}%";
}
}
- if ($filter->getNotAuthor()) {
+ if ($filter->getNotAuthor() !== null) {
foreach ($filter->getNotAuthor() as $author) {
$sub_search .= 'AND ' . $alias . 'author NOT LIKE ? ';
$values[] = "%{$author}%";
}
}
- if ($filter->getNotIntitle()) {
+ if ($filter->getNotIntitle() !== null) {
foreach ($filter->getNotIntitle() as $title) {
$sub_search .= 'AND ' . $alias . 'title NOT LIKE ? ';
$values[] = "%{$title}%";
}
}
- if ($filter->getNotTags()) {
+ if ($filter->getNotTags() !== null) {
foreach ($filter->getNotTags() as $tag) {
$sub_search .= 'AND ' . static::sqlConcat('TRIM(' . $alias . 'tags) ', " ' #'") . ' NOT LIKE ? ';
$values[] = "%{$tag} #%";
}
}
- if ($filter->getNotInurl()) {
+ if ($filter->getNotInurl() !== null) {
foreach ($filter->getNotInurl() as $url) {
$sub_search .= 'AND ' . $alias . 'link NOT LIKE ? ';
$values[] = "%{$url}%";
}
}
- if ($filter->getSearch()) {
+ if ($filter->getSearch() !== null) {
foreach ($filter->getSearch() as $search_value) {
if (static::isCompressed()) { // MySQL-only
$sub_search .= 'AND CONCAT(' . $alias . 'title, UNCOMPRESS(' . $alias . 'content_bin)) LIKE ? ';
@@ -972,7 +974,7 @@ SQL;
}
}
}
- if ($filter->getNotSearch()) {
+ if ($filter->getNotSearch() !== null) {
foreach ($filter->getNotSearch() as $search_value) {
if (static::isCompressed()) { // MySQL-only
$sub_search .= 'AND CONCAT(' . $alias . 'title, UNCOMPRESS(' . $alias . 'content_bin)) NOT LIKE ? ';
@@ -1163,9 +1165,11 @@ SQL;
$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
if ($stm) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
- yield FreshRSS_Entry::fromArray($row);
+ if (is_array($row)) {
+ /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
+ yield FreshRSS_Entry::fromArray($row);
+ }
}
}
}
@@ -1206,9 +1210,11 @@ SQL;
return;
}
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
- yield FreshRSS_Entry::fromArray($row);
+ if (is_array($row)) {
+ /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
+ * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
+ yield FreshRSS_Entry::fromArray($row);
+ }
}
}
@@ -1283,7 +1289,7 @@ SQL;
$affected = 0;
$guidsChunks = array_chunk($guids, FreshRSS_DatabaseDAO::MAX_VARIABLE_NUMBER);
foreach ($guidsChunks as $guidsChunk) {
- $affected += $this->updateLastSeen($id_feed, $guidsChunk, $mtime);
+ $affected += ($this->updateLastSeen($id_feed, $guidsChunk, $mtime) ?: 0);
}
return $affected;
}
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 66feb567b..1a87d03be 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -80,7 +80,7 @@ SQL;
//if (true) { //Speed heuristics //TODO: Not implemented yet for SQLite (so always call IDs one by one)
$affected = 0;
foreach ($ids as $id) {
- $affected += $this->markRead($id, $is_read);
+ $affected += ($this->markRead($id, $is_read) ?: 0);
}
return $affected;
//}
diff --git a/app/Models/Factory.php b/app/Models/Factory.php
index eef3c81f5..f69c7f6aa 100644
--- a/app/Models/Factory.php
+++ b/app/Models/Factory.php
@@ -14,7 +14,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createCategoryDao(?string $username = null): FreshRSS_CategoryDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_CategoryDAOSQLite($username);
default:
@@ -26,7 +26,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createFeedDao(?string $username = null): FreshRSS_FeedDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_FeedDAOSQLite($username);
default:
@@ -38,7 +38,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createEntryDao(?string $username = null): FreshRSS_EntryDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_EntryDAOSQLite($username);
case 'pgsql':
@@ -52,7 +52,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createTagDao(?string $username = null): FreshRSS_TagDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_TagDAOSQLite($username);
case 'pgsql':
@@ -66,7 +66,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createStatsDAO(?string $username = null): FreshRSS_StatsDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_StatsDAOSQLite($username);
case 'pgsql':
@@ -80,7 +80,7 @@ class FreshRSS_Factory {
* @throws Minz_ConfigurationNamespaceException|Minz_PDOConnectionException
*/
public static function createDatabaseDAO(?string $username = null): FreshRSS_DatabaseDAO {
- switch (FreshRSS_Context::$system_conf->db['type'] ?? '') {
+ switch (FreshRSS_Context::systemConf()->db['type'] ?? '') {
case 'sqlite':
return new FreshRSS_DatabaseDAOSQLite($username);
case 'pgsql':
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index dbe6aaa73..024a7841a 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -82,7 +82,7 @@ class FreshRSS_Feed extends Minz_Model {
public function hash(): string {
if ($this->hash == '') {
- $salt = FreshRSS_Context::$system_conf->salt;
+ $salt = FreshRSS_Context::systemConf()->salt;
$this->hash = hash('crc32b', $salt . $this->url);
}
return $this->hash;
@@ -126,7 +126,7 @@ class FreshRSS_Feed extends Minz_Model {
return $simplePie == null ? [] : iterator_to_array($this->loadEntries($simplePie));
}
public function name(bool $raw = false): string {
- return $raw || $this->name != '' ? $this->name : preg_replace('%^https?://(www[.])?%i', '', $this->url);
+ return $raw || $this->name != '' ? $this->name : (preg_replace('%^https?://(www[.])?%i', '', $this->url) ?? '');
}
/** @return string HTML-encoded URL of the Web site of the feed */
public function website(): string {
@@ -179,7 +179,7 @@ class FreshRSS_Feed extends Minz_Model {
if ($raw) {
$ttl = $this->ttl;
if ($this->mute && FreshRSS_Feed::TTL_DEFAULT === $ttl) {
- $ttl = FreshRSS_Context::$user_conf ? FreshRSS_Context::$user_conf->ttl_default : 3600;
+ $ttl = FreshRSS_Context::userConf()->ttl_default;
}
return $ttl * ($this->mute ? -1 : 1);
}
@@ -331,7 +331,7 @@ class FreshRSS_Feed extends Minz_Model {
} else {
$url = htmlspecialchars_decode($this->url, ENT_QUOTES);
if ($this->httpAuth != '') {
- $url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
+ $url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url) ?? '';
}
$simplePie = customSimplePie($this->attributes());
if (substr($url, -11) === '#force_feed') {
@@ -342,7 +342,7 @@ class FreshRSS_Feed extends Minz_Model {
if (!$loadDetails) { //Only activates auto-discovery when adding a new feed
$simplePie->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE);
}
- if ($this->attributes('clear_cache')) {
+ if ($this->attributeBoolean('clear_cache')) {
// Do not use `$simplePie->enable_cache(false);` as it would prevent caching in multiuser context
$this->clearCache();
}
@@ -370,7 +370,7 @@ class FreshRSS_Feed extends Minz_Model {
if ($loadDetails) {
// si on a utilisé l’auto-discover, notre url va avoir changé
- $subscribe_url = $simplePie->subscribe_url(false);
+ $subscribe_url = $simplePie->subscribe_url(false) ?? '';
if ($this->name(true) === '') {
//HTML to HTML-PRE //ENT_COMPAT except '&'
@@ -385,11 +385,11 @@ class FreshRSS_Feed extends Minz_Model {
}
} else {
//The case of HTTP 301 Moved Permanently
- $subscribe_url = $simplePie->subscribe_url(true);
+ $subscribe_url = $simplePie->subscribe_url(true) ?? '';
}
$clean_url = SimplePie_Misc::url_remove_credentials($subscribe_url);
- if ($subscribe_url !== null && $subscribe_url !== $url) {
+ if ($subscribe_url !== '' && $subscribe_url !== $url) {
$this->_url($clean_url);
}
@@ -411,7 +411,7 @@ class FreshRSS_Feed extends Minz_Model {
$testGuids = [];
$guids = [];
$links = [];
- $hadBadGuids = $this->attributes('hasBadGuids');
+ $hadBadGuids = $this->attributeBoolean('hasBadGuids');
$items = $simplePie->get_items();
if (empty($items)) {
@@ -426,7 +426,10 @@ class FreshRSS_Feed extends Minz_Model {
$hasUniqueGuids &= empty($testGuids['_' . $guid]);
$testGuids['_' . $guid] = true;
$guids[] = $guid;
- $links[] = $item->get_permalink();
+ $permalink = $item->get_permalink();
+ if ($permalink != null) {
+ $links[] = $permalink;
+ }
}
if ($hadBadGuids != !$hasUniqueGuids) {
@@ -444,7 +447,7 @@ class FreshRSS_Feed extends Minz_Model {
/** @return Traversable<FreshRSS_Entry> */
public function loadEntries(SimplePie $simplePie): Traversable {
- $hasBadGuids = $this->attributes('hasBadGuids');
+ $hasBadGuids = $this->attributeBoolean('hasBadGuids');
$items = $simplePie->get_items();
if (empty($items)) {
@@ -560,15 +563,15 @@ class FreshRSS_Feed extends Minz_Model {
$title == '' ? '' : $title,
$authorNames,
$content == '' ? '' : $content,
- $link == '' ? '' : $link,
+ $link == null ? '' : $link,
$date ?: time()
);
$entry->_tags($tags);
$entry->_feed($this);
if (!empty($attributeThumbnail['url'])) {
- $entry->_attributes('thumbnail', $attributeThumbnail);
+ $entry->_attribute('thumbnail', $attributeThumbnail);
}
- $entry->_attributes('enclosures', $attributeEnclosures);
+ $entry->_attribute('enclosures', $attributeEnclosures);
$entry->hash(); //Must be computed before loading full content
$entry->loadCompleteContent(); // Optionally load full content for truncated feeds
@@ -587,11 +590,14 @@ class FreshRSS_Feed extends Minz_Model {
if ($this->httpAuth != '') {
$feedSourceUrl = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $feedSourceUrl);
}
+ if ($feedSourceUrl == null) {
+ return null;
+ }
// Same naming conventions than https://rss-bridge.github.io/rss-bridge/Bridge_API/XPathAbstract.html
// https://rss-bridge.github.io/rss-bridge/Bridge_API/BridgeAbstract.html#collectdata
/** @var array<string,string> $xPathSettings */
- $xPathSettings = $this->attributes('xpath');
+ $xPathSettings = $this->attributeArray('xpath');
$xPathFeedTitle = $xPathSettings['feedTitle'] ?? '';
$xPathItem = $xPathSettings['item'] ?? '';
$xPathItemTitle = $xPathSettings['itemTitle'] ?? '';
@@ -725,9 +731,9 @@ class FreshRSS_Feed extends Minz_Model {
* @throws JsonException
*/
public function keepMaxUnread() {
- $keepMaxUnread = $this->attributes('keep_max_n_unread');
+ $keepMaxUnread = $this->attributeInt('keep_max_n_unread');
if ($keepMaxUnread === null) {
- $keepMaxUnread = FreshRSS_Context::$user_conf->mark_when['max_n_unread'];
+ $keepMaxUnread = FreshRSS_Context::userConf()->mark_when['max_n_unread'];
}
return is_int($keepMaxUnread) && $keepMaxUnread >= 0 ? $keepMaxUnread : null;
}
@@ -754,9 +760,9 @@ class FreshRSS_Feed extends Minz_Model {
* @return int|false the number of lines affected, or false if not applicable
*/
public function markAsReadUponGone(bool $upstreamIsEmpty, int $maxTimestamp = 0) {
- $readUponGone = $this->attributes('read_upon_gone');
+ $readUponGone = $this->attributeBoolean('read_upon_gone');
if ($readUponGone === null) {
- $readUponGone = FreshRSS_Context::$user_conf->mark_when['gone'];
+ $readUponGone = FreshRSS_Context::userConf()->mark_when['gone'];
}
if (!$readUponGone) {
return false;
@@ -782,13 +788,15 @@ class FreshRSS_Feed extends Minz_Model {
* @return int|false
*/
public function cleanOldEntries() {
- $archiving = $this->attributes('archiving');
- if ($archiving == null) {
+ /** @var array<string,bool|int|string>|null $archiving */
+ $archiving = $this->attributeArray('archiving');
+ if ($archiving === null) {
$catDAO = FreshRSS_Factory::createCategoryDao();
$category = $catDAO->searchById($this->categoryId);
- $archiving = $category == null ? null : $category->attributes('archiving');
- if ($archiving == null) {
- $archiving = FreshRSS_Context::$user_conf->archiving;
+ $archiving = $category === null ? null : $category->attributeArray('archiving');
+ /** @var array<string,bool|int|string>|null $archiving */
+ if ($archiving === null) {
+ $archiving = FreshRSS_Context::userConf()->archiving;
}
}
if (is_array($archiving)) {
@@ -850,7 +858,7 @@ class FreshRSS_Feed extends Minz_Model {
$hubFilename = PSHB_PATH . '/feeds/' . sha1($url) . '/!hub.json';
if ($hubFile = @file_get_contents($hubFilename)) {
$hubJson = json_decode($hubFile, true);
- if ($hubJson && empty($hubJson['error']) &&
+ if (is_array($hubJson) && empty($hubJson['error']) &&
(empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) {
return true;
}
@@ -862,8 +870,8 @@ class FreshRSS_Feed extends Minz_Model {
$url = $this->selfUrl ?: $this->url;
$hubFilename = PSHB_PATH . '/feeds/' . sha1($url) . '/!hub.json';
$hubFile = @file_get_contents($hubFilename);
- $hubJson = $hubFile ? json_decode($hubFile, true) : [];
- if (!isset($hubJson['error']) || $hubJson['error'] !== $error) {
+ $hubJson = is_string($hubFile) ? json_decode($hubFile, true) : null;
+ if (is_array($hubJson) && !isset($hubJson['error']) || $hubJson['error'] !== $error) {
$hubJson['error'] = $error;
file_put_contents($hubFilename, json_encode($hubJson));
Minz_Log::warning('Set error to ' . ($error ? 1 : 0) . ' for ' . $url, PSHB_LOG);
@@ -876,13 +884,13 @@ class FreshRSS_Feed extends Minz_Model {
*/
public function pubSubHubbubPrepare() {
$key = '';
- if (Minz_Request::serverIsPublic(FreshRSS_Context::$system_conf->base_url) &&
+ if (Minz_Request::serverIsPublic(FreshRSS_Context::systemConf()->base_url) &&
$this->hubUrl && $this->selfUrl && @is_dir(PSHB_PATH)) {
$path = PSHB_PATH . '/feeds/' . sha1($this->selfUrl);
$hubFilename = $path . '/!hub.json';
if ($hubFile = @file_get_contents($hubFilename)) {
$hubJson = json_decode($hubFile, true);
- if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
+ if (!is_array($hubJson) || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
$text = 'Invalid JSON for WebSub: ' . $this->url;
Minz_Log::warning($text);
Minz_Log::warning($text, PSHB_LOG);
@@ -901,7 +909,7 @@ class FreshRSS_Feed extends Minz_Model {
}
} else {
@mkdir($path, 0770, true);
- $key = sha1($path . FreshRSS_Context::$system_conf->salt);
+ $key = sha1($path . FreshRSS_Context::systemConf()->salt);
$hubJson = [
'hub' => $this->hubUrl,
'key' => $key,
@@ -913,7 +921,7 @@ class FreshRSS_Feed extends Minz_Model {
Minz_Log::debug($text);
Minz_Log::debug($text, PSHB_LOG);
}
- $currentUser = Minz_User::name();
+ $currentUser = Minz_User::name() ?? '';
if (FreshRSS_user_Controller::checkUsername($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) {
touch($path . '/' . $currentUser . '.txt');
}
@@ -928,7 +936,7 @@ class FreshRSS_Feed extends Minz_Model {
} else {
$url = $this->url; //Always use current URL during unsubscribe
}
- if ($url && (Minz_Request::serverIsPublic(FreshRSS_Context::$system_conf->base_url) || !$state)) {
+ if ($url && (Minz_Request::serverIsPublic(FreshRSS_Context::systemConf()->base_url) || !$state)) {
$hubFilename = PSHB_PATH . '/feeds/' . sha1($url) . '/!hub.json';
$hubFile = @file_get_contents($hubFilename);
if ($hubFile === false) {
@@ -936,7 +944,7 @@ class FreshRSS_Feed extends Minz_Model {
return false;
}
$hubJson = json_decode($hubFile, true);
- if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key']) || empty($hubJson['hub'])) {
+ if (!is_array($hubJson) || empty($hubJson['key']) || !ctype_xdigit($hubJson['key']) || empty($hubJson['hub'])) {
Minz_Log::warning('Invalid JSON for WebSub: ' . $this->url);
return false;
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index ac844217a..895d2d333 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -118,7 +118,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
// Merge existing and import attributes
$existingAttributes = $feed_search->attributes();
$importAttributes = $feed->attributes();
- $feed->_attributes('', array_replace_recursive($existingAttributes, $importAttributes));
+ $feed->_attributes(array_replace_recursive($existingAttributes, $importAttributes));
// Update some values of the existing feed using the import
$values = [
@@ -190,11 +190,12 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
/**
+ * @param non-empty-string $key
* @param string|array<mixed>|bool|int|null $value
* @return int|false
*/
public function updateFeedAttribute(FreshRSS_Feed $feed, string $key, $value) {
- $feed->_attributes($key, $value);
+ $feed->_attribute($key, $value);
return $this->updateFeed(
$feed->id(),
['attributes' => $feed->attributes()]
@@ -236,6 +237,9 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if ($newCat === null) {
$newCat = $catDAO->getDefault();
}
+ if ($newCat === null) {
+ return false;
+ }
$sql = 'UPDATE `_feed` SET category=? WHERE category=?';
$stm = $this->pdo->prepare($sql);
@@ -305,6 +309,8 @@ SQL;
return;
}
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':int,'url':string,'kind':int,'category':int,'name':string,'website':string,'description':string,'lastUpdate':int,'priority'?:int,
+ * 'pathEntries'?:string,'httpAuth':string,'error':int|bool,'ttl'?:int,'attributes'?:string} $row */
yield $row;
}
}
@@ -393,12 +399,12 @@ SQL;
}
}
- /** @return array<string> */
+ /** @return array<int,string> */
public function listTitles(int $id, int $limit = 0): array {
$sql = 'SELECT title FROM `_entry` WHERE id_feed=:id_feed ORDER BY id DESC'
. ($limit < 1 ? '' : ' LIMIT ' . intval($limit));
$res = $this->fetchColumn($sql, 0, [':id_feed' => $id]) ?? [];
- /** @var array<string> $res */
+ /** @var array<int,string> $res */
return $res;
}
@@ -609,7 +615,7 @@ SQL;
$myFeed->_httpAuth(base64_decode($dao['httpAuth'] ?? '', true) ?: '');
$myFeed->_error($dao['error'] ?? 0);
$myFeed->_ttl($dao['ttl'] ?? FreshRSS_Feed::TTL_DEFAULT);
- $myFeed->_attributes('', $dao['attributes'] ?? '');
+ $myFeed->_attributes($dao['attributes'] ?? '');
$myFeed->_nbNotRead($dao['cache_nbUnreads'] ?? -1);
$myFeed->_nbEntries($dao['cache_nbEntries'] ?? -1);
if (isset($dao['id'])) {
diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php
index 6487d5100..bf5a79fe7 100644
--- a/app/Models/FilterAction.php
+++ b/app/Models/FilterAction.php
@@ -44,7 +44,7 @@ class FreshRSS_FilterAction {
/** @param array|mixed|null $json */
public static function fromJSON($json): ?FreshRSS_FilterAction {
- if (!empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) {
+ if (is_array($json) && !empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) {
return new FreshRSS_FilterAction(new FreshRSS_BooleanSearch($json['search']), $json['actions']);
}
return null;
diff --git a/app/Models/FilterActionsTrait.php b/app/Models/FilterActionsTrait.php
index 869992b21..aefb549af 100644
--- a/app/Models/FilterActionsTrait.php
+++ b/app/Models/FilterActionsTrait.php
@@ -15,13 +15,11 @@ trait FreshRSS_FilterActionsTrait {
private function filterActions(): array {
if (empty($this->filterActions)) {
$this->filterActions = [];
- $filters = $this->attributes('filters');
- if (is_array($filters)) {
- foreach ($filters as $filter) {
- $filterAction = FreshRSS_FilterAction::fromJSON($filter);
- if ($filterAction != null) {
- $this->filterActions[] = $filterAction;
- }
+ $filters = $this->attributeArray('filters') ?? [];
+ foreach ($filters as $filter) {
+ $filterAction = FreshRSS_FilterAction::fromJSON($filter);
+ if ($filterAction != null) {
+ $this->filterActions[] = $filterAction;
}
}
}
@@ -33,12 +31,12 @@ trait FreshRSS_FilterActionsTrait {
*/
private function _filterActions(?array $filterActions): void {
$this->filterActions = $filterActions;
- if (is_array($this->filterActions) && !empty($this->filterActions)) {
- $this->_attributes('filters', array_map(static function (?FreshRSS_FilterAction $af) {
+ if ($this->filterActions !== null && !empty($this->filterActions)) {
+ $this->_attribute('filters', array_map(static function (?FreshRSS_FilterAction $af) {
return $af == null ? null : $af->toJSON();
}, $this->filterActions));
} else {
- $this->_attributes('filters', null);
+ $this->_attribute('filters', null);
}
}
diff --git a/app/Models/FormAuth.php b/app/Models/FormAuth.php
index 83fb60e3c..a8b4dab8a 100644
--- a/app/Models/FormAuth.php
+++ b/app/Models/FormAuth.php
@@ -23,7 +23,7 @@ class FreshRSS_FormAuth {
$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
$mtime = @filemtime($token_file) ?: 0;
- $limits = FreshRSS_Context::$system_conf->limits;
+ $limits = FreshRSS_Context::systemConf()->limits;
$cookie_duration = empty($limits['cookie_duration']) ? FreshRSS_Auth::DEFAULT_COOKIE_DURATION : $limits['cookie_duration'];
if ($mtime + $cookie_duration < time()) {
// Token has expired (> cookie_duration) or does not exist.
@@ -42,7 +42,7 @@ class FreshRSS_FormAuth {
private static function renewCookie(string $token) {
$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
if (touch($token_file)) {
- $limits = FreshRSS_Context::$system_conf->limits;
+ $limits = FreshRSS_Context::systemConf()->limits;
$cookie_duration = empty($limits['cookie_duration']) ? FreshRSS_Auth::DEFAULT_COOKIE_DURATION : $limits['cookie_duration'];
$expire = time() + $cookie_duration;
Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
@@ -54,7 +54,7 @@ class FreshRSS_FormAuth {
/** @return string|false */
public static function makeCookie(string $username, string $password_hash) {
do {
- $token = sha1(FreshRSS_Context::$system_conf->salt . $username . uniqid('' . mt_rand(), true));
+ $token = sha1(FreshRSS_Context::systemConf()->salt . $username . uniqid('' . mt_rand(), true));
$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
} while (file_exists($token_file));
@@ -78,7 +78,7 @@ class FreshRSS_FormAuth {
}
public static function purgeTokens(): void {
- $limits = FreshRSS_Context::$system_conf->limits;
+ $limits = FreshRSS_Context::systemConf()->limits;
$cookie_duration = empty($limits['cookie_duration']) ? FreshRSS_Auth::DEFAULT_COOKIE_DURATION : $limits['cookie_duration'];
$oldest = time() - $cookie_duration;
foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) {
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 0e0bad623..9bdc255e3 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -248,8 +248,8 @@ WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
SQL;
- $res = $this->fetchAssoc($sql);
/** @var array<array{'label':string,'data':int}>|null @res */
+ $res = $this->fetchAssoc($sql);
return $res == null ? [] : $res;
}
diff --git a/app/Models/SystemConfiguration.php b/app/Models/SystemConfiguration.php
index 294ca1e3a..3efe33b15 100644
--- a/app/Models/SystemConfiguration.php
+++ b/app/Models/SystemConfiguration.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
* @property bool $allow_anonymous
* @property bool $allow_anonymous_refresh
* @property-read bool $allow_referrer
- * @property-read bool $allow_robots
+ * @property bool $allow_robots
* @property bool $api_enabled
* @property string $archiving
* @property 'form'|'http_auth'|'none' $auth_type
@@ -16,7 +16,7 @@ declare(strict_types=1);
* @property bool $force_email_validation
* @property-read bool $http_auth_auto_register
* @property-read string $http_auth_auto_register_email_field
- * @property-read string $language
+ * @property string $language
* @property array<string,int> $limits
* @property-read string $logo_html
* @property-read string $meta_description
@@ -25,6 +25,7 @@ declare(strict_types=1);
* @property-read bool $simplepie_syslog_enabled
* @property bool $unsafe_autologin_enabled
* @property array<string> $trusted_sources
+ * @property array<string,array<string,mixed>> $extensions
*/
final class FreshRSS_SystemConfiguration extends Minz_Configuration {
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index 8587e576f..fba27dc7b 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -96,11 +96,12 @@ SQL;
}
/**
+ * @param non-empty-string $key
* @param mixed $value
* @return int|false
*/
public function updateTagAttribute(FreshRSS_Tag $tag, string $key, $value) {
- $tag->_attributes($key, $value);
+ $tag->_attribute($key, $value);
return $this->updateTagAttributes($tag->id(), $tag->attributes());
}
@@ -134,6 +135,7 @@ SQL;
return;
}
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ /** @var array{'id':int,'name':string,'attributes'?:array<string,mixed>} $row */
yield $row;
}
}
@@ -410,7 +412,7 @@ SQL;
$tag = new FreshRSS_Tag($dao['name']);
$tag->_id($dao['id']);
if (!empty($dao['attributes'])) {
- $tag->_attributes('', $dao['attributes']);
+ $tag->_attributes($dao['attributes']);
}
if (isset($dao['unreads'])) {
$tag->_nbUnread($dao['unreads']);
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index 53ae1dc27..fab29e986 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -38,11 +38,12 @@ class FreshRSS_Themes extends Minz_Model {
if (file_exists($json_filename)) {
$content = file_get_contents($json_filename) ?: '';
$res = json_decode($content, true);
- if ($res &&
+ if (is_array($res) &&
!empty($res['name']) &&
isset($res['files']) &&
is_array($res['files'])) {
$res['id'] = $theme_id;
+ /** @var array{'id':string,'name':string,'author':string,'description':string,'version':float|string,'files':array<string>,'theme-color'?:string|array{'dark'?:string,'light'?:string,'default'?:string}} */
return $res;
}
}
@@ -155,7 +156,7 @@ class FreshRSS_Themes extends Minz_Model {
}
if ($type == self::ICON_DEFAULT) {
- if ((FreshRSS_Context::$user_conf && FreshRSS_Context::$user_conf->icons_as_emojis)
+ if ((FreshRSS_Context::hasUserConf() && FreshRSS_Context::userConf()->icons_as_emojis)
// default to emoji alternate for some icons
) {
$type = self::ICON_EMOJI;
diff --git a/app/Models/UserConfiguration.php b/app/Models/UserConfiguration.php
index 0aec3a05f..a1e0dbbaa 100644
--- a/app/Models/UserConfiguration.php
+++ b/app/Models/UserConfiguration.php
@@ -70,6 +70,7 @@ declare(strict_types=1);
* @property-read bool $unsafe_autologin_enabled
* @property string $view_mode
* @property array<string,mixed> $volatile
+ * @property array<string,array<string,mixed>> $extensions
*/
final class FreshRSS_UserConfiguration extends Minz_Configuration {
use FreshRSS_FilterActionsTrait;
@@ -81,22 +82,37 @@ final class FreshRSS_UserConfiguration extends Minz_Configuration {
}
/**
- * @phpstan-return ($key is non-empty-string ? mixed : array<string,mixed>)
- * @return array<string,mixed>|mixed|null
+ * @param non-empty-string $key
+ * @return array<int|string,mixed>|null
*/
- public function attributes(string $key = '') {
- if ($key === '') {
- return []; // Not implemented for user configuration
- } else {
- return parent::param($key, null);
- }
+ public function attributeArray(string $key): ?array {
+ $a = parent::param($key, null);
+ return is_array($a) ? $a : null;
}
- /** @param string|array<mixed>|bool|int|null $value Value, not HTML-encoded */
- public function _attributes(string $key, $value = null): void {
- if ($key == '') {
- return; // Not implemented for user configuration
- }
+ /** @param non-empty-string $key */
+ public function attributeBool(string $key): ?bool {
+ $a = parent::param($key, null);
+ return is_bool($a) ? $a : null;
+ }
+
+ /** @param non-empty-string $key */
+ public function attributeInt(string $key): ?int {
+ $a = parent::param($key, null);
+ return is_numeric($a) ? (int)$a : null;
+ }
+
+ /** @param non-empty-string $key */
+ public function attributeString(string $key): ?string {
+ $a = parent::param($key, null);
+ return is_string($a) ? $a : null;
+ }
+
+ /**
+ * @param non-empty-string $key
+ * @param array<string,mixed>|mixed|null $value Value, not HTML-encoded
+ */
+ public function _attribute(string $key, $value = null): void {
parent::_param($key, $value);
}
}