mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-01-30 16:11:11 -05:00
* Add move to next unread Label on mark as read. The Labels, unlike the Feeds and Categories, don't move to the next unread when "move to next unread on mark all as read" user feature is enabled. Labels are more complex than Feeds and Categories because Entries can be in more than Label at a time. So when marking all Entries in the Label as read, it can cause other Labels to end up with all their Entries marked as read as well. The calculation of what the next Label/Feed/Category is to jump to normally happens when generating the link for the "Mark as Read" buttons, but it can't for Labels. To address the problem for Labels, use a placeholder value during the pre-calculation of the "Mark as Read" button link. When that placeholder value is encountered during the "Mark as Read" action, the next Label with unread Entries will be calculated immediately after the mark as read action has been processed. Fix all the translations of the 'jump_next' text to remove the '(feed or categories' part that no longer applies. Attempt to fix the inconsistent Russian, Italian, and Polish translations of 'jump_next' text, which phrased the '(feed or categories)' part differently. * Minor code formattting * Fixes * Optimize next label lookup. Only get the tag list once, and actually error check that it returned successfully. Fix a typo in a comment as well. * Fix fallback when all Labels are read. Fix the missing check for whether we're in the fallback case or not. * Update app/i18n/ru/conf.php * Update app/Controllers/entryController.php * Minor changes * One more minor --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
597 lines
19 KiB
PHP
597 lines
19 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* The context object handles the current configuration file and different
|
|
* useful functions associated to the current view state.
|
|
*/
|
|
final class FreshRSS_Context {
|
|
|
|
/**
|
|
* @var array<int,FreshRSS_Category>
|
|
*/
|
|
private static array $categories = [];
|
|
/**
|
|
* @var array<int,FreshRSS_Tag>
|
|
*/
|
|
private static array $tags = [];
|
|
public static string $name = '';
|
|
public static string $description = '';
|
|
public static int $total_unread = 0;
|
|
public static int $total_important_unread = 0;
|
|
|
|
/** @var array{'all':int,'read':int,'unread':int} */
|
|
public static array $total_starred = [
|
|
'all' => 0,
|
|
'read' => 0,
|
|
'unread' => 0,
|
|
];
|
|
|
|
public static int $get_unread = 0;
|
|
|
|
/** @var array{'all':bool,'starred':bool,'important':bool,'feed':int|false,'category':int|false,'tag':int|false,'tags':bool} */
|
|
public static array $current_get = [
|
|
'all' => false,
|
|
'starred' => false,
|
|
'important' => false,
|
|
'feed' => false,
|
|
'category' => false,
|
|
'tag' => false,
|
|
'tags' => false,
|
|
];
|
|
|
|
public static string $next_get = 'a';
|
|
public static int $state = 0;
|
|
/**
|
|
* @phpstan-var 'ASC'|'DESC'
|
|
*/
|
|
public static string $order = 'DESC';
|
|
public static int $number = 0;
|
|
public static int $offset = 0;
|
|
public static FreshRSS_BooleanSearch $search;
|
|
public static string $first_id = '';
|
|
public static string $next_id = '';
|
|
public static string $id_max = '';
|
|
public static int $sinceHours = 0;
|
|
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): 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');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws FreshRSS_Context_Exception
|
|
*/
|
|
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.
|
|
*/
|
|
public static function initUser(string $username = '', bool $userMustExist = true): void {
|
|
FreshRSS_Context::$user_conf = null;
|
|
if (!isset($_SESSION)) {
|
|
Minz_Session::init('FreshRSS');
|
|
}
|
|
|
|
Minz_Session::lock();
|
|
if ($username == '') {
|
|
$username = Minz_User::name() ?? '';
|
|
}
|
|
if (($username === Minz_User::INTERNAL_USER || FreshRSS_user_Controller::checkUsername($username)) &&
|
|
(!$userMustExist || FreshRSS_user_Controller::userExists($username))) {
|
|
try {
|
|
//TODO: Keep in session what we need instead of always reloading from disk
|
|
FreshRSS_Context::$user_conf = FreshRSS_UserConfiguration::init(
|
|
USERS_PATH . '/' . $username . '/config.php',
|
|
FRESHRSS_PATH . '/config-user.default.php');
|
|
|
|
Minz_User::change($username);
|
|
} catch (Exception $ex) {
|
|
Minz_Log::warning($ex->getMessage(), USERS_PATH . '/_/' . LOG_FILENAME);
|
|
}
|
|
}
|
|
if (FreshRSS_Context::$user_conf == null) {
|
|
Minz_Session::_params([
|
|
'loginOk' => false,
|
|
Minz_User::CURRENT_USER => false,
|
|
]);
|
|
}
|
|
Minz_Session::unlock();
|
|
|
|
if (FreshRSS_Context::$user_conf == null) {
|
|
return;
|
|
}
|
|
|
|
FreshRSS_Context::$search = new FreshRSS_BooleanSearch('');
|
|
|
|
//Legacy
|
|
$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;
|
|
if ($oldEntries > 0) {
|
|
$archiving['keep_period'] = 'P' . $oldEntries . 'M';
|
|
}
|
|
if ($keepMin > 0) {
|
|
$archiving['keep_min'] = $keepMin;
|
|
} elseif ($keepMin == -1) { //Infinite
|
|
$archiving['keep_period'] = false;
|
|
$archiving['keep_min'] = false;
|
|
}
|
|
FreshRSS_Context::$user_conf->archiving = $archiving;
|
|
}
|
|
|
|
//Legacy < 1.16.1
|
|
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';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws FreshRSS_Context_Exception
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/** @return array<int,FreshRSS_Category> */
|
|
public static function categories(): array {
|
|
if (empty(self::$categories)) {
|
|
$catDAO = FreshRSS_Factory::createCategoryDao();
|
|
self::$categories = $catDAO->listSortedCategories(true, false);
|
|
}
|
|
return self::$categories;
|
|
}
|
|
|
|
/** @return array<int,FreshRSS_Feed> */
|
|
public static function feeds(): array {
|
|
return FreshRSS_Category::findFeeds(self::categories());
|
|
}
|
|
|
|
/** @return array<int,FreshRSS_Tag> */
|
|
public static function labels(bool $precounts = false): array {
|
|
if (empty(self::$tags) || $precounts) {
|
|
$tagDAO = FreshRSS_Factory::createTagDao();
|
|
self::$tags = $tagDAO->listTags($precounts) ?: [];
|
|
}
|
|
return self::$tags;
|
|
}
|
|
|
|
/**
|
|
* This action updates the Context object by using request parameters.
|
|
*
|
|
* HTTP GET request parameters are:
|
|
* - state (default: conf->default_view)
|
|
* - search (default: empty string)
|
|
* - order (default: conf->sort_order)
|
|
* - nb (default: conf->posts_per_page)
|
|
* - next (default: empty string)
|
|
* - hours (default: 0)
|
|
* @throws FreshRSS_Context_Exception
|
|
* @throws Minz_ConfigurationNamespaceException
|
|
* @throws Minz_PDOConnectionException
|
|
*/
|
|
public static function updateUsingRequest(bool $computeStatistics): void {
|
|
if ($computeStatistics && self::$total_unread === 0) {
|
|
// Update number of read / unread variables.
|
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
|
self::$total_starred = $entryDAO->countUnreadReadFavorites();
|
|
self::$total_unread = FreshRSS_Category::countUnread(self::categories(), FreshRSS_Feed::PRIORITY_MAIN_STREAM);
|
|
self::$total_important_unread = FreshRSS_Category::countUnread(self::categories(), FreshRSS_Feed::PRIORITY_IMPORTANT);
|
|
}
|
|
|
|
self::_get(Minz_Request::paramString('get') ?: 'a');
|
|
|
|
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 (FreshRSS_Context::userConf()->default_view === 'all') {
|
|
self::$state |= FreshRSS_Entry::STATE_ALL;
|
|
} elseif (FreshRSS_Context::userConf()->default_view === 'adaptive' && self::$get_unread <= 0) {
|
|
self::$state |= FreshRSS_Entry::STATE_READ;
|
|
}
|
|
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') ?: FreshRSS_Context::userConf()->sort_order;
|
|
self::$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
|
|
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(
|
|
FreshRSS_Context::userConf()->max_posts_per_rss,
|
|
FreshRSS_Context::userConf()->posts_per_page);
|
|
}
|
|
self::$offset = Minz_Request::paramInt('offset');
|
|
self::$first_id = Minz_Request::paramString('next');
|
|
self::$sinceHours = Minz_Request::paramInt('hours');
|
|
}
|
|
|
|
/**
|
|
* Returns if the current state includes $state parameter.
|
|
*/
|
|
public static function isStateEnabled(int $state): int {
|
|
return self::$state & $state;
|
|
}
|
|
|
|
/**
|
|
* Returns the current state with or without $state parameter.
|
|
*/
|
|
public static function getRevertState(int $state): int {
|
|
if (self::$state & $state) {
|
|
return self::$state & ~$state;
|
|
}
|
|
return self::$state | $state;
|
|
}
|
|
|
|
/**
|
|
* Return the current get as a string or an array.
|
|
*
|
|
* If $array is true, the first item of the returned value is 'f' or 'c' or 't' and the second is the id.
|
|
* @phpstan-return ($asArray is true ? array{'a'|'c'|'f'|'i'|'s'|'t'|'T',bool|int} : string)
|
|
* @return string|array{string,bool|int}
|
|
*/
|
|
public static function currentGet(bool $asArray = false): string|array {
|
|
if (self::$current_get['all']) {
|
|
return $asArray ? ['a', true] : 'a';
|
|
} elseif (self::$current_get['important']) {
|
|
return $asArray ? ['i', true] : 'i';
|
|
} elseif (self::$current_get['starred']) {
|
|
return $asArray ? ['s', true] : 's';
|
|
} elseif (self::$current_get['feed']) {
|
|
if ($asArray) {
|
|
return ['f', self::$current_get['feed']];
|
|
} else {
|
|
return 'f_' . self::$current_get['feed'];
|
|
}
|
|
} elseif (self::$current_get['category']) {
|
|
if ($asArray) {
|
|
return ['c', self::$current_get['category']];
|
|
} else {
|
|
return 'c_' . self::$current_get['category'];
|
|
}
|
|
} elseif (self::$current_get['tag']) {
|
|
if ($asArray) {
|
|
return ['t', self::$current_get['tag']];
|
|
} else {
|
|
return 't_' . self::$current_get['tag'];
|
|
}
|
|
} elseif (self::$current_get['tags']) {
|
|
return $asArray ? ['T', true] : 'T';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @return bool true if the current request targets all feeds (main view), false otherwise.
|
|
*/
|
|
public static function isAll(): bool {
|
|
return self::$current_get['all'] != false;
|
|
}
|
|
|
|
/**
|
|
* @return bool true if the current request targets important feeds, false otherwise.
|
|
*/
|
|
public static function isImportant(): bool {
|
|
return self::$current_get['important'] != false;
|
|
}
|
|
|
|
/**
|
|
* @return bool true if the current request targets a category, false otherwise.
|
|
*/
|
|
public static function isCategory(): bool {
|
|
return self::$current_get['category'] != false;
|
|
}
|
|
|
|
/**
|
|
* @return bool true if the current request targets a feed (and not a category or all articles), false otherwise.
|
|
*/
|
|
public static function isFeed(): bool {
|
|
return self::$current_get['feed'] != false;
|
|
}
|
|
|
|
/**
|
|
* @return bool true if the current request targets a tag (though not all tags), false otherwise.
|
|
*/
|
|
public static function isTag(): bool {
|
|
return self::$current_get['tag'] != false;
|
|
}
|
|
|
|
/**
|
|
* @return bool whether $get parameter corresponds to the $current_get attribute.
|
|
*/
|
|
public static function isCurrentGet(string $get): bool {
|
|
$type = substr($get, 0, 1);
|
|
$id = substr($get, 2);
|
|
|
|
switch ($type) {
|
|
case 'a':
|
|
return self::$current_get['all'];
|
|
case 'i':
|
|
return self::$current_get['important'];
|
|
case 's':
|
|
return self::$current_get['starred'];
|
|
case 'f':
|
|
return self::$current_get['feed'] == $id;
|
|
case 'c':
|
|
return self::$current_get['category'] == $id;
|
|
case 't':
|
|
return self::$current_get['tag'] == $id;
|
|
case 'T':
|
|
return self::$current_get['tags'] || self::$current_get['tag'];
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the current $get attribute.
|
|
*
|
|
* Valid $get parameter are:
|
|
* - a
|
|
* - s
|
|
* - f_<feed id>
|
|
* - c_<category id>
|
|
* - t_<tag id>
|
|
*
|
|
* $name and $get_unread attributes are also updated as $next_get
|
|
* Raise an exception if id or $get is invalid.
|
|
* @throws FreshRSS_Context_Exception
|
|
* @throws Minz_ConfigurationNamespaceException
|
|
* @throws Minz_PDOConnectionException
|
|
*/
|
|
public static function _get(string $get): void {
|
|
$type = $get[0];
|
|
$id = (int)substr($get, 2);
|
|
|
|
if (empty(self::$categories)) {
|
|
$catDAO = FreshRSS_Factory::createCategoryDao();
|
|
$details = $type === 'f'; // Load additional feed details in the case of feed view
|
|
self::$categories = $catDAO->listCategories(true, $details);
|
|
}
|
|
|
|
switch ($type) {
|
|
case 'a':
|
|
self::$current_get['all'] = true;
|
|
self::$name = _t('index.feed.title');
|
|
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 = 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 = FreshRSS_Context::systemConf()->meta_description;
|
|
self::$get_unread = self::$total_starred['unread'];
|
|
|
|
// Update state if favorite is not yet enabled.
|
|
self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE;
|
|
break;
|
|
case 'f':
|
|
// We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description
|
|
$feed = FreshRSS_Context::systemConf()->allow_robots ? null : FreshRSS_Category::findFeed(self::$categories, $id);
|
|
if ($feed === null) {
|
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
|
$feed = $feedDAO->searchById($id);
|
|
if ($feed === null) {
|
|
throw new FreshRSS_Context_Exception('Invalid feed: ' . $id);
|
|
}
|
|
}
|
|
self::$current_get['feed'] = $id;
|
|
self::$current_get['category'] = $feed->categoryId();
|
|
self::$name = $feed->name();
|
|
self::$description = $feed->description();
|
|
self::$get_unread = $feed->nbNotRead();
|
|
break;
|
|
case 'c':
|
|
// We try to find the corresponding category.
|
|
self::$current_get['category'] = $id;
|
|
if (!isset(self::$categories[$id])) {
|
|
$catDAO = FreshRSS_Factory::createCategoryDao();
|
|
$cat = $catDAO->searchById($id);
|
|
if ($cat === null) {
|
|
throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
|
|
}
|
|
self::$categories[$id] = $cat;
|
|
} else {
|
|
$cat = self::$categories[$id];
|
|
}
|
|
self::$name = $cat->name();
|
|
self::$get_unread = $cat->nbNotRead();
|
|
break;
|
|
case 't':
|
|
// We try to find the corresponding tag.
|
|
self::$current_get['tag'] = $id;
|
|
if (!isset(self::$tags[$id])) {
|
|
$tagDAO = FreshRSS_Factory::createTagDao();
|
|
$tag = $tagDAO->searchById($id);
|
|
if ($tag === null) {
|
|
throw new FreshRSS_Context_Exception('Invalid tag: ' . $id);
|
|
}
|
|
self::$tags[$id] = $tag;
|
|
} else {
|
|
$tag = self::$tags[$id];
|
|
}
|
|
self::$name = $tag->name();
|
|
self::$get_unread = $tag->nbUnread();
|
|
break;
|
|
case 'T':
|
|
$tagDAO = FreshRSS_Factory::createTagDao();
|
|
self::$current_get['tags'] = true;
|
|
self::$name = _t('index.menu.tags');
|
|
self::$get_unread = $tagDAO->countNotRead();
|
|
break;
|
|
default:
|
|
throw new FreshRSS_Context_Exception('Invalid getter: ' . $get);
|
|
}
|
|
|
|
self::_nextGet();
|
|
}
|
|
|
|
/**
|
|
* Set the value of $next_get attribute.
|
|
*/
|
|
private static function _nextGet(): void {
|
|
$get = self::currentGet();
|
|
// By default, $next_get == $get
|
|
self::$next_get = $get;
|
|
|
|
if (empty(self::$categories)) {
|
|
$catDAO = FreshRSS_Factory::createCategoryDao();
|
|
self::$categories = $catDAO->listCategories(true);
|
|
}
|
|
|
|
if (FreshRSS_Context::userConf()->onread_jump_next && strlen($get) > 2) {
|
|
$another_unread_id = '';
|
|
$found_current_get = false;
|
|
switch ($get[0]) {
|
|
case 'f':
|
|
// We search the next unread feed with the following priorities: next in same category, or previous in same category, or next, or previous.
|
|
foreach (self::$categories as $cat) {
|
|
$sameCat = false;
|
|
foreach ($cat->feeds() as $feed) {
|
|
if ($found_current_get) {
|
|
if ($feed->nbNotRead() > 0) {
|
|
$another_unread_id = $feed->id();
|
|
break 2;
|
|
}
|
|
} elseif ($feed->id() == self::$current_get['feed']) {
|
|
$found_current_get = true;
|
|
} elseif ($feed->nbNotRead() > 0) {
|
|
$another_unread_id = $feed->id();
|
|
$sameCat = true;
|
|
}
|
|
}
|
|
if ($found_current_get && $sameCat) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there is no more unread feed, show main stream
|
|
self::$next_get = $another_unread_id == '' ? 'a' : 'f_' . $another_unread_id;
|
|
break;
|
|
case 'c':
|
|
// We search the next category with at least one unread article.
|
|
foreach (self::$categories as $cat) {
|
|
if ($cat->id() == self::$current_get['category']) {
|
|
// Here is our current category! Next one could be our
|
|
// champion if it has unread articles.
|
|
$found_current_get = true;
|
|
continue;
|
|
}
|
|
|
|
if ($cat->nbNotRead() > 0) {
|
|
$another_unread_id = $cat->id();
|
|
if ($found_current_get) {
|
|
// Unread articles and the current category has
|
|
// already been found? Leave the loop!
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there is no more unread category, show main stream
|
|
self::$next_get = $another_unread_id == '' ? 'a' : 'c_' . $another_unread_id;
|
|
break;
|
|
case 't':
|
|
// We can't know what the next unread tag is because entries can be in multiple tags
|
|
// so marking all entries in a tag can indirectly mark all entries in multiple tags.
|
|
// Default is to return to the current tag, so mark it as next_get = 'a' instead when
|
|
// userconf -> onread_jump_next so the readAction knows to jump to the next unread
|
|
// tag.
|
|
self::$next_get = 'a';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if the auto remove is available in the current context.
|
|
* This feature is available if:
|
|
* - it is activated in the configuration
|
|
* - the "read" state is not enable
|
|
* - the "unread" state is enable
|
|
*/
|
|
public static function isAutoRemoveAvailable(): bool {
|
|
if (!FreshRSS_Context::userConf()->auto_remove_article) {
|
|
return false;
|
|
}
|
|
if (self::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
|
|
return false;
|
|
}
|
|
if (!self::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine if the "sticky post" option is enabled. It can be enable
|
|
* by the user when it is selected in the configuration page or by the
|
|
* application when the context allows to auto-remove articles when they
|
|
* are read.
|
|
*/
|
|
public static function isStickyPostEnabled(): bool {
|
|
if (FreshRSS_Context::userConf()->sticky_post) {
|
|
return true;
|
|
}
|
|
if (self::isAutoRemoveAvailable()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function defaultTimeZone(): string {
|
|
$timezone = ini_get('date.timezone');
|
|
return $timezone != false ? $timezone : 'UTC';
|
|
}
|
|
}
|