diff options
Diffstat (limited to 'classes')
-rwxr-xr-x | classes/api.php | 21 | ||||
-rwxr-xr-x | classes/article.php | 2 | ||||
-rw-r--r-- | classes/config.php | 27 | ||||
-rw-r--r-- | classes/db/prefs.php | 167 | ||||
-rw-r--r-- | classes/dbupdater.php | 3 | ||||
-rw-r--r-- | classes/digest.php | 10 | ||||
-rw-r--r-- | classes/errors.php | 5 | ||||
-rwxr-xr-x | classes/feeds.php | 124 | ||||
-rwxr-xr-x | classes/handler/public.php | 23 | ||||
-rwxr-xr-x | classes/logger.php | 27 | ||||
-rw-r--r-- | classes/logger/adapter.php | 4 | ||||
-rwxr-xr-x | classes/logger/sql.php | 12 | ||||
-rw-r--r-- | classes/logger/stdout.php | 6 | ||||
-rw-r--r-- | classes/logger/syslog.php | 6 | ||||
-rw-r--r-- | classes/mailer.php | 2 | ||||
-rw-r--r-- | classes/opml.php | 2 | ||||
-rwxr-xr-x | classes/pluginhost.php | 8 | ||||
-rwxr-xr-x | classes/pref/feeds.php | 24 | ||||
-rwxr-xr-x | classes/pref/filters.php | 2 | ||||
-rw-r--r-- | classes/pref/prefs.php | 268 | ||||
-rw-r--r-- | classes/pref/system.php | 2 | ||||
-rw-r--r-- | classes/prefs.php | 407 | ||||
-rwxr-xr-x | classes/rpc.php | 132 | ||||
-rwxr-xr-x | classes/rssutils.php | 121 | ||||
-rw-r--r-- | classes/sanitizer.php | 2 | ||||
-rw-r--r-- | classes/timehelper.php | 12 | ||||
-rw-r--r-- | classes/userhelper.php | 24 |
27 files changed, 867 insertions, 576 deletions
diff --git a/classes/api.php b/classes/api.php index 18f9c83b5..a0ee773c1 100755 --- a/classes/api.php +++ b/classes/api.php @@ -36,7 +36,7 @@ class API extends Handler { return false; } - if (!empty($_SESSION["uid"]) && $method != "logout" && !get_pref('ENABLE_API_ACCESS')) { + if (!empty($_SESSION["uid"]) && $method != "logout" && !get_pref(Prefs::ENABLE_API_ACCESS)) { $this->_wrap(self::STATUS_ERR, array("error" => self::E_API_DISABLED)); return false; } @@ -59,25 +59,24 @@ class API extends Handler { } function login() { - @session_destroy(); - @session_start(); + + if (session_status() == PHP_SESSION_ACTIVE) { + session_destroy(); + } + + session_start(); $login = clean($_REQUEST["user"]); $password = clean($_REQUEST["password"]); - $password_base64 = base64_decode(clean($_REQUEST["password"])); if (Config::get(Config::SINGLE_USER_MODE)) $login = "admin"; if ($uid = UserHelper::find_user_by_login($login)) { - if (get_pref("ENABLE_API_ACCESS", $uid)) { - if (UserHelper::authenticate($login, $password, false, Auth_Base::AUTH_SERVICE_API)) { // try login with normal password + if (get_pref(Prefs::ENABLE_API_ACCESS, $uid)) { + if (UserHelper::authenticate($login, $password, false, Auth_Base::AUTH_SERVICE_API)) { $this->_wrap(self::STATUS_OK, array("session_id" => session_id(), "api_level" => self::API_LEVEL)); - } else if (UserHelper::authenticate($login, $password_base64, false, Auth_Base::AUTH_SERVICE_API)) { // else try with base64_decoded password - $this->_wrap(self::STATUS_OK, array("session_id" => session_id(), - "api_level" => self::API_LEVEL)); - } else { // else we are not logged in - user_error("Failed login attempt for $login from " . UserHelper::get_user_ip(), E_USER_WARNING); + } else { $this->_wrap(self::STATUS_ERR, array("error" => self::E_LOGIN_ERROR)); } } else { diff --git a/classes/article.php b/classes/article.php index 6baf8f068..d8ae97257 100755 --- a/classes/article.php +++ b/classes/article.php @@ -349,7 +349,7 @@ class Article extends Handler_Protected { $rv['can_inline'] = isset($_SESSION["uid"]) && empty($_SESSION["bw_limit"]) && - !get_pref("STRIP_IMAGES") && + !get_pref(Prefs::STRIP_IMAGES) && ($always_display_enclosures || !preg_match("/<img/i", $article_content)); $rv['inline_text_only'] = $hide_images && $rv['can_inline']; diff --git a/classes/config.php b/classes/config.php index effbb78ad..ee1d3cb4a 100644 --- a/classes/config.php +++ b/classes/config.php @@ -107,14 +107,19 @@ class Config { private static $instance; private $params = []; + private $schema_version = null; - public static function get_instance() { + public static function get_instance() : Config { if (self::$instance == null) self::$instance = new self(); return self::$instance; } + private function __clone() { + // + } + function __construct() { $ref = new ReflectionClass(get_class($this)); @@ -124,12 +129,26 @@ class Config { list ($defval, $deftype) = $this::_DEFAULTS[$const]; - $this->params[$cvalue] = [ $this->cast_to(!empty($override) ? $override : $defval, $deftype), $deftype ]; + $this->params[$cvalue] = [ self::cast_to(!empty($override) ? $override : $defval, $deftype), $deftype ]; } } } - private function cast_to(string $value, int $type_hint) { + static function get_schema_version(bool $nocache = false) { + return self::get_instance()->_schema_version($nocache); + } + + function _schema_version(bool $nocache = false) { + if (empty($this->schema_version) || $nocache) { + $row = Db::pdo()->query("SELECT schema_version FROM ttrss_version")->fetch(); + + $this->schema_version = (int) $row["schema_version"]; + } + + return $this->schema_version; + } + + static function cast_to(string $value, int $type_hint) { switch ($type_hint) { case self::T_BOOL: return sql_bool_to_bool($value); @@ -149,7 +168,7 @@ class Config { private function _add(string $param, string $default, int $type_hint) { $override = getenv($this::_ENVVAR_PREFIX . $param); - $this->params[$param] = [ $this->cast_to(!empty($override) ? $override : $default, $type_hint), $type_hint ]; + $this->params[$param] = [ self::cast_to(!empty($override) ? $override : $default, $type_hint), $type_hint ]; } static function add(string $param, string $default, int $type_hint = Config::T_STRING) { diff --git a/classes/db/prefs.php b/classes/db/prefs.php index 44581dbcb..821216622 100644 --- a/classes/db/prefs.php +++ b/classes/db/prefs.php @@ -1,173 +1,12 @@ <?php class Db_Prefs { - private $pdo; - private static $instance; - private $cache; - - function __construct() { - $this->pdo = Db::pdo(); - $this->cache = []; - $this->cache_prefs(); - } - - private function __clone() { - // - } - - public static function get() { - if (self::$instance == null) - self::$instance = new self(); - - return self::$instance; - } - - private function cache_prefs() { - if (!empty($_SESSION["uid"])) { - $profile = $_SESSION["profile"] ?? false; - - if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null; - - $sth = $this->pdo->prepare("SELECT up.pref_name, pt.type_name, up.value - FROM ttrss_user_prefs up - JOIN ttrss_prefs p ON (up.pref_name = p.pref_name) - JOIN ttrss_prefs_types pt ON (p.type_id = pt.id) - WHERE - up.pref_name NOT LIKE '_MOBILE%' AND - (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND - owner_uid = :uid"); - - $sth->execute([":profile" => $profile, ":uid" => $_SESSION["uid"]]); - - while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { - $pref_name = $row["pref_name"]; - - $this->cache[$pref_name] = [ - "type" => $row["type_name"], - "value" => $row["value"] - ]; - } - } - } + // this class is a stub for the time being (to be removed) function read($pref_name, $user_id = false, $die_on_error = false) { - - if (!$user_id) { - $user_id = $_SESSION["uid"]; - $profile = $_SESSION["profile"] ?? false; - } else { - $profile = false; - } - - if ($user_id == ($_SESSION['uid'] ?? false) && isset($this->cache[$pref_name])) { - $tuple = $this->cache[$pref_name]; - return $this->convert($tuple["value"], $tuple["type"]); - } - - if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null; - - $sth = $this->pdo->prepare("SELECT up.pref_name, pt.type_name, up.value - FROM ttrss_user_prefs up - JOIN ttrss_prefs p ON (up.pref_name = p.pref_name) - JOIN ttrss_prefs_types pt ON (p.type_id = pt.id) - WHERE - up.pref_name = :pref_name AND - (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND - owner_uid = :uid"); - - $sth->execute([":uid" => $user_id, ":profile" => $profile, ":pref_name" => $pref_name]); - - if ($row = $sth->fetch(PDO::FETCH_ASSOC)) { - $value = $row["value"]; - $type_name = $row["type_name"]; - - if ($user_id == ($_SESSION["uid"] ?? false)) { - $this->cache[$pref_name] = [ - "type" => $row["type_name"], - "value" => $row["value"] - ]; - } - - return $this->convert($value, $type_name); - - } else if ($die_on_error) { - user_error("Failed retrieving preference $pref_name for user $user_id", E_USER_ERROR); - } else { - user_error("Failed retrieving preference $pref_name for user $user_id", E_USER_WARNING); - } - - return null; - } - - function convert($value, $type_name) { - if ($type_name == "bool") { - return $value == "true"; - } else if ($type_name == "integer") { - return (int)$value; - } else { - return $value; - } + return get_pref($pref_name, $user_id); } function write($pref_name, $value, $user_id = false, $strip_tags = true) { - if ($strip_tags) $value = strip_tags($value); - - if (!$user_id) { - $user_id = $_SESSION["uid"]; - @$profile = $_SESSION["profile"] ?? false; - } else { - $profile = null; - } - - if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null; - - $type_name = ""; - $current_value = ""; - - if (isset($this->cache[$pref_name])) { - $type_name = $this->cache[$pref_name]["type"]; - $current_value = $this->cache[$pref_name]["value"]; - } - - if (!$type_name) { - $sth = $this->pdo->prepare("SELECT type_name - FROM ttrss_prefs,ttrss_prefs_types - WHERE pref_name = ? AND type_id = ttrss_prefs_types.id"); - $sth->execute([$pref_name]); - - if ($row = $sth->fetch()) - $type_name = $row["type_name"]; - - } else if ($current_value == $value) { - return; - } - - if ($type_name) { - if ($type_name == "bool") { - if ($value == "1" || $value == "true") { - $value = "true"; - } else { - $value = "false"; - } - } else if ($type_name == "integer") { - $value = (int)$value; - } - - if ($pref_name == 'USER_TIMEZONE' && $value == '') { - $value = 'UTC'; - } - - $sth = $this->pdo->prepare("UPDATE ttrss_user_prefs SET - value = :value WHERE pref_name = :pref_name - AND (profile = :profile OR (:profile IS NULL AND profile IS NULL)) - AND owner_uid = :uid"); - - $sth->execute([":pref_name" => $pref_name, ":value" => $value, ":uid" => $user_id, ":profile" => $profile]); - - if ($user_id == $_SESSION["uid"]) { - $this->cache[$pref_name]["type"] = $type_name; - $this->cache[$pref_name]["value"] = $value; - } - } + return set_pref($pref_name, $value, $user_id, $strip_tags); } - } diff --git a/classes/dbupdater.php b/classes/dbupdater.php index e923c7fcb..d1df31b40 100644 --- a/classes/dbupdater.php +++ b/classes/dbupdater.php @@ -12,8 +12,7 @@ class DbUpdater { } function get_schema_version() { - $row = $this->pdo->query("SELECT schema_version FROM ttrss_version")->fetch(); - return (int) $row['schema_version']; + return Config::get_schema_version(true); } function is_update_required() { diff --git a/classes/digest.php b/classes/digest.php index a6a0c47de..26ca5221f 100644 --- a/classes/digest.php +++ b/classes/digest.php @@ -21,8 +21,8 @@ class Digest while ($line = $res->fetch()) { - if (@get_pref('DIGEST_ENABLE', $line['id'], false)) { - $preferred_ts = strtotime(get_pref('DIGEST_PREFERRED_TIME', $line['id'], '00:00')); + if (get_pref(Prefs::DIGEST_ENABLE, $line['id'])) { + $preferred_ts = strtotime(get_pref(Prefs::DIGEST_PREFERRED_TIME, $line['id'])); // try to send digests within 2 hours of preferred time if ($preferred_ts && time() >= $preferred_ts && @@ -31,7 +31,7 @@ class Digest Debug::log("Sending digest for UID:" . $line['id'] . " - " . $line["email"]); - $do_catchup = get_pref('DIGEST_CATCHUP', $line['id'], false); + $do_catchup = get_pref(Prefs::DIGEST_CATCHUP, $line['id']); global $tz_offset; @@ -86,7 +86,7 @@ class Digest $tpl->readTemplateFromFile("digest_template_html.txt"); $tpl_t->readTemplateFromFile("digest_template.txt"); - $user_tz_string = get_pref('USER_TIMEZONE', $user_id); + $user_tz_string = get_pref(Prefs::USER_TIMEZONE, $user_id); $local_ts = TimeHelper::convert_timestamp(time(), 'UTC', $user_tz_string); $tpl->setVariable('CUR_DATE', date('Y/m/d', $local_ts)); @@ -152,7 +152,7 @@ class Digest $updated = TimeHelper::make_local_datetime($line['last_updated'], false, $user_id); - if (get_pref('ENABLE_FEED_CATS', $user_id)) { + if (get_pref(Prefs::ENABLE_FEED_CATS, $user_id)) { $line['feed_title'] = $line['cat_title'] . " / " . $line['feed_title']; } diff --git a/classes/errors.php b/classes/errors.php index be175418e..3599c2639 100644 --- a/classes/errors.php +++ b/classes/errors.php @@ -5,8 +5,9 @@ class Errors { const E_UNKNOWN_METHOD = "E_UNKNOWN_METHOD"; const E_UNKNOWN_PLUGIN = "E_UNKNOWN_PLUGIN"; const E_SCHEMA_MISMATCH = "E_SCHEMA_MISMATCH"; + const E_URL_SCHEME_MISMATCH = "E_URL_SCHEME_MISMATCH"; - static function to_json(string $code) { - return json_encode(["error" => ["code" => $code]]); + static function to_json(string $code, array $params = []) { + return json_encode(["error" => ["code" => $code, "params" => $params]]); } } diff --git a/classes/feeds.php b/classes/feeds.php index ba2719f48..583cb3e12 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -108,7 +108,7 @@ class Feeds extends Handler_Protected { $this->_mark_timestamp("db query"); - $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && + $vfeed_group_enabled = get_pref(Prefs::VFEED_GROUP_BY_FEED) && !(in_array($feed, self::NEVER_GROUP_FEEDS) && !$cat_view); $result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed @@ -167,7 +167,7 @@ class Feeds extends Handler_Protected { ++$headlines_count; - if (!get_pref('SHOW_CONTENT_PREVIEW')) { + if (!get_pref(Prefs::SHOW_CONTENT_PREVIEW)) { $line["content_preview"] = ""; } else { $line["content_preview"] = "— " . truncate_string(strip_tags($line["content"]), 250); @@ -208,15 +208,19 @@ class Feeds extends Handler_Protected { if ($label_cache) { if ($label_cache["no-labels"] ?? false == 1) - $labels = array(); + $labels = []; else $labels = $label_cache; } + + $line["labels"] = $labels; + } else { + $line["labels"] = []; } - if (!is_array($labels)) $labels = Article::_get_labels($id); + /*if (!is_array($labels)) $labels = Article::_get_labels($id); - $line["labels"] = Article::_get_labels($id); + $line["labels"] = Article::_get_labels($id);*/ if (count($topmost_article_ids) < 3) { array_push($topmost_article_ids, $id); @@ -262,22 +266,26 @@ class Feeds extends Handler_Protected { $this->_mark_timestamp(" note"); - if (!get_pref("CDM_EXPANDED")) { + if (!get_pref(Prefs::CDM_EXPANDED)) { $line["cdm_excerpt"] = "<span class='collapse'> <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; - if (get_pref('SHOW_CONTENT_PREVIEW')) { + if (get_pref(Prefs::SHOW_CONTENT_PREVIEW)) { $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; } } $this->_mark_timestamp(" pre-enclosures"); - $line["enclosures"] = Article::_format_enclosures($id, - $line["always_display_enclosures"], - $line["content"], - $line["hide_images"]); + if ($line["num_enclosures"] > 0) { + $line["enclosures"] = Article::_format_enclosures($id, + $line["always_display_enclosures"], + $line["content"], + $line["hide_images"]); + } else { + $line["enclosures"] = [ 'formatted' => '', 'entries' => [] ]; + } $this->_mark_timestamp(" enclosures"); @@ -292,9 +300,11 @@ class Feeds extends Handler_Protected { if ($line["tag_cache"]) $tags = explode(",", $line["tag_cache"]); else - $tags = false; + $tags = []; - $line["tags"] = Article::_get_tags($line["id"], false, $line["tag_cache"]); + $line["tags"] = $tags; + + //$line["tags"] = Article::_get_tags($line["id"], false, $line["tag_cache"]); $this->_mark_timestamp(" tags"); @@ -324,7 +334,7 @@ class Feeds extends Handler_Protected { /* we don't need those */ foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color", - "uuid", "label_cache", "yyiw"] as $k) + "uuid", "label_cache", "yyiw", "num_enclosures"] as $k) unset($line[$k]); array_push($reply['content'], $line); @@ -413,7 +423,7 @@ class Feeds extends Handler_Protected { $feed = $_REQUEST["feed"]; $method = $_REQUEST["m"] ?? ""; - $view_mode = $_REQUEST["view_mode"]; + $view_mode = $_REQUEST["view_mode"] ?? ""; $limit = 30; $cat_view = $_REQUEST["cat"] == "true"; $next_unread_feed = $_REQUEST["nuf"] ?? 0; @@ -459,8 +469,8 @@ class Feeds extends Handler_Protected { return; } - set_pref("_DEFAULT_VIEW_MODE", $view_mode); - set_pref("_DEFAULT_VIEW_ORDER_BY", $order_by); + set_pref(Prefs::_DEFAULT_VIEW_MODE, $view_mode); + set_pref(Prefs::_DEFAULT_VIEW_ORDER_BY, $order_by); /* bump login timestamp if needed */ if (time() - $_SESSION["last_login_update"] > 3600) { @@ -499,7 +509,7 @@ class Feeds extends Handler_Protected { "disable_cache" => (bool) $disable_cache]; // this is parsed by handleRpcJson() on first viewfeed() to set cdm expanded, etc - $reply['runtime-info'] = RPC::make_runtime_info(); + $reply['runtime-info'] = RPC::_make_runtime_info(); print json_encode($reply); } @@ -573,7 +583,7 @@ class Feeds extends Handler_Protected { "show_language" => Config::get(Config::DB_TYPE) == "pgsql", "show_syntax_help" => count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0, "all_languages" => Pref_Feeds::get_ts_languages(), - "default_language" => get_pref('DEFAULT_SEARCH_LANGUAGE') + "default_language" => get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE) ]); } @@ -799,7 +809,7 @@ class Feeds extends Handler_Protected { if ($feed == -3) { - $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE"); + $intl = (int) get_pref(Prefs::FRESH_ARTICLE_MAX_AGE); if (Config::get(Config::DB_TYPE) == "pgsql") { $match_part = "date_entered > NOW() - INTERVAL '$intl hour' "; @@ -892,7 +902,7 @@ class Feeds extends Handler_Protected { } else if ($n_feed == -3) { $match_part = "unread = true AND score >= 0"; - $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + $intl = (int) get_pref(Prefs::FRESH_ARTICLE_MAX_AGE, $owner_uid); if (Config::get(Config::DB_TYPE) == "pgsql") { $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; @@ -1478,7 +1488,7 @@ class Feeds extends Handler_Protected { } else if ($feed == -3) { // fresh virtual feed $query_strategy_part = "unread = true AND score >= 0"; - $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + $intl = (int) get_pref(Prefs::FRESH_ARTICLE_MAX_AGE, $owner_uid); if (Config::get(Config::DB_TYPE) == "pgsql") { $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; @@ -1564,9 +1574,15 @@ class Feeds extends Handler_Protected { $first_id = 0; + if (Config::get(Config::DB_TYPE) == "pgsql") { + $yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; + } else { + $yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw"; + } + if (is_numeric($feed)) { // proper override_order applied above - if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { + if ($vfeed_query_part && !$ignore_vfeed_group && get_pref(Prefs::VFEED_GROUP_BY_FEED, $owner_uid)) { if (!(in_array($feed, self::NEVER_GROUP_BY_DATE) && !$cat_view)) { $yyiw_desc = $order_by == "date_reverse" ? "" : "desc"; @@ -1583,7 +1599,7 @@ class Feeds extends Handler_Protected { } if (!$allow_archived) { - $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds"; + $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id), ttrss_feeds"; $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND"; } else { @@ -1601,16 +1617,19 @@ class Feeds extends Handler_Protected { if (Config::get(Config::DB_TYPE) == "pgsql") { $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; - $yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; $distinct_columns = str_replace("desc", "", strtolower($order_by)); $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; } else { $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND"; - $yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw"; $distinct_qpart = "DISTINCT"; //fallback } + // except for Labels category + if (get_pref(Prefs::HEADLINES_NO_DISTINCT, $owner_uid) && !($feed == -2 && $cat_view)) { + $distinct_qpart = ""; + } + if (!$search && !$skip_first_id_check) { // if previous topmost article id changed that means our current pagination is no longer valid $query = "SELECT @@ -1675,7 +1694,8 @@ class Feeds extends Handler_Protected { last_marked, last_published, $vfeed_query_part $content_query_part - author,score + author,score, + (SELECT count(id) FROM ttrss_enclosures WHERE post_id = ttrss_entries.id) AS num_enclosures FROM $from_qpart WHERE @@ -1699,41 +1719,45 @@ class Feeds extends Handler_Protected { } else { // browsing by tag - if (Config::get(Config::DB_TYPE) == "pgsql") { - $distinct_columns = str_replace("desc", "", strtolower($order_by)); - $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; + if (get_pref(Prefs::HEADLINES_NO_DISTINCT, $owner_uid)) { + $distinct_qpart = ""; } else { - $distinct_qpart = "DISTINCT"; //fallback + if (Config::get(Config::DB_TYPE) == "pgsql") { + $distinct_columns = str_replace("desc", "", strtolower($order_by)); + $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; + } else { + $distinct_qpart = "DISTINCT"; //fallback + } } $query = "SELECT $distinct_qpart + ttrss_entries.id AS id, date_entered, + $yyiw_qpart, guid, - note, - ttrss_entries.id as id, - title, + ttrss_entries.title, updated, - unread, - feed_id, - marked, - published, + label_cache, + tag_cache, + always_display_enclosures, + site_url, + note, num_comments, comments, int_id, - tag_cache, - label_cache, - link, - lang, uuid, - last_read, - (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images, + lang, + hide_images, + unread,feed_id,marked,published,link,last_read, last_marked, last_published, $since_id_part $vfeed_query_part $content_query_part - author, score - FROM ttrss_entries, ttrss_user_entries, ttrss_tags + author, score, + (SELECT count(id) FROM ttrss_enclosures WHERE post_id = ttrss_entries.id) AS num_enclosures + FROM ttrss_entries, ttrss_user_entries, ttrss_tags, ttrss_feeds WHERE + ttrss_feeds.id = ttrss_user_entries.feed_id AND ref_id = ttrss_entries.id AND ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND post_int_id = int_id AND @@ -1975,7 +1999,7 @@ class Feeds extends Handler_Protected { $purge_unread = true; $purge_interval = Config::get(Config::FORCE_ARTICLE_PURGE); } else { - $purge_unread = get_pref("PURGE_UNREAD_ARTICLES", $owner_uid, false); + $purge_unread = get_pref(Prefs::PURGE_UNREAD_ARTICLES, $owner_uid); } $purge_interval = (int) $purge_interval; @@ -2038,7 +2062,7 @@ class Feeds extends Handler_Protected { $owner_uid = $row["owner_uid"]; if ($purge_interval == 0) - $purge_interval = get_pref('PURGE_OLD_DAYS', $owner_uid, false); + $purge_interval = get_pref(Prefs::PURGE_OLD_DAYS, $owner_uid); return $purge_interval; } else { @@ -2058,7 +2082,7 @@ class Feeds extends Handler_Protected { if ($search_language) $search_language = $pdo->quote(mb_strtolower($search_language)); else - $search_language = $pdo->quote(mb_strtolower(get_pref('DEFAULT_SEARCH_LANGUAGE', $owner_uid))); + $search_language = $pdo->quote(mb_strtolower(get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE, $owner_uid))); foreach ($keywords as $k) { if (strpos($k, "-") === 0) { @@ -2166,7 +2190,7 @@ class Feeds extends Handler_Protected { default: if (strpos($k, "@") === 0) { - $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); + $user_tz_string = get_pref(Prefs::USER_TIMEZONE, $_SESSION['uid']); $orig_ts = strtotime(substr($k, 1)); $k = date("Y-m-d", TimeHelper::convert_timestamp($orig_ts, $user_tz_string, 'UTC')); diff --git a/classes/handler/public.php b/classes/handler/public.php index 42be6f713..e4572382e 100755 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -40,7 +40,7 @@ class Handler_Public extends Handler { if (!$is_cat && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { - $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); + $user_plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid); $tmppluginhost = new PluginHost(); $tmppluginhost->load(Config::get(Config::PLUGINS), PluginHost::KIND_ALL); @@ -354,20 +354,22 @@ class Handler_Public extends Handler { $remember_me = clean($_POST["remember_me"] ?? false); $safe_mode = checkbox_to_sql_bool(clean($_POST["safe_mode"] ?? false)); - if ($remember_me) { - @session_set_cookie_params(Config::get(Config::SESSION_COOKIE_LIFETIME)); - } else { - @session_set_cookie_params(0); + if (session_status() != PHP_SESSION_ACTIVE) { + if ($remember_me) { + session_set_cookie_params(Config::get(Config::SESSION_COOKIE_LIFETIME)); + } else { + session_set_cookie_params(0); + } } if (UserHelper::authenticate($login, $password)) { $_POST["password"] = ""; if (get_schema_version() >= 120) { - $_SESSION["language"] = get_pref("USER_LANGUAGE", $_SESSION["uid"]); + $_SESSION["language"] = get_pref(Prefs::USER_LANGUAGE, $_SESSION["uid"]); } - $_SESSION["ref_schema_version"] = get_schema_version(true); + $_SESSION["ref_schema_version"] = get_schema_version(); $_SESSION["bw_limit"] = !!clean($_POST["bw_limit"] ?? false); $_SESSION["safe_mode"] = $safe_mode; @@ -388,12 +390,11 @@ class Handler_Public extends Handler { } else { // start an empty session to deliver login error message - @session_start(); + if (session_status() != PHP_SESSION_ACTIVE) + session_start(); if (!isset($_SESSION["login_error_msg"])) $_SESSION["login_error_msg"] = __("Incorrect username or password"); - - user_error("Failed login attempt for $login from " . UserHelper::get_user_ip(), E_USER_WARNING); } $return = clean($_REQUEST['return']); @@ -613,7 +614,7 @@ class Handler_Public extends Handler { function dbupdate() { startup_gettext(); - if (!Config::get(Config::SINGLE_USER_MODE) && $_SESSION["access_level"] < 10) { + if (!Config::get(Config::SINGLE_USER_MODE) && ($_SESSION["access_level"] ?? 0) < 10) { $_SESSION["login_error_msg"] = __("Your access level is insufficient to run this script."); $this->_render_login_form(); exit; diff --git a/classes/logger.php b/classes/logger.php index 6cc33314d..864b66743 100755 --- a/classes/logger.php +++ b/classes/logger.php @@ -3,7 +3,7 @@ class Logger { private static $instance; private $adapter; - public static $errornames = array( + const ERROR_NAMES = [ 1 => 'E_ERROR', 2 => 'E_WARNING', 4 => 'E_PARSE', @@ -19,10 +19,14 @@ class Logger { 4096 => 'E_RECOVERABLE_ERROR', 8192 => 'E_DEPRECATED', 16384 => 'E_USER_DEPRECATED', - 32767 => 'E_ALL'); + 32767 => 'E_ALL']; - function log_error($errno, $errstr, $file, $line, $context) { - if ($errno == E_NOTICE) return false; + static function log_error(int $errno, string $errstr, string $file, int $line, $context) { + return self::get_instance()->_log_error($errno, $errstr, $file, $line, $context); + } + + private function _log_error($errno, $errstr, $file, $line, $context) { + //if ($errno == E_NOTICE) return false; if ($this->adapter) return $this->adapter->log_error($errno, $errstr, $file, $line, $context); @@ -30,7 +34,11 @@ class Logger { return false; } - function log($errno, $errstr, $context = "") { + static function log(int $errno, string $errstr, $context = "") { + return self::get_instance()->_log($errno, $errstr, $context); + } + + private function _log(int $errno, string $errstr, $context = "") { if ($this->adapter) return $this->adapter->log_error($errno, $errstr, '', 0, $context); else @@ -55,13 +63,20 @@ class Logger { default: $this->adapter = false; } + + if ($this->adapter && !implements_interface($this->adapter, "Logger_Adapter")) + user_error("Adapter for LOG_DESTINATION: " . Config::LOG_DESTINATION . " does not implement required interface.", E_USER_ERROR); } - public static function get() { + private static function get_instance() : Logger { if (self::$instance == null) self::$instance = new self(); return self::$instance; } + static function get() : Logger { + user_error("Please don't use Logger::get(), call Logger::log(...) instead.", E_USER_DEPRECATED); + return self::get_instance(); + } } diff --git a/classes/logger/adapter.php b/classes/logger/adapter.php new file mode 100644 index 000000000..79f641441 --- /dev/null +++ b/classes/logger/adapter.php @@ -0,0 +1,4 @@ +<?php +interface Logger_Adapter { + function log_error(int $errno, string $errstr, string $file, int $line, $context); +}
\ No newline at end of file diff --git a/classes/logger/sql.php b/classes/logger/sql.php index ad7fdecb2..d21934aa6 100755 --- a/classes/logger/sql.php +++ b/classes/logger/sql.php @@ -1,17 +1,15 @@ <?php -class Logger_SQL { +class Logger_SQL implements Logger_Adapter { private $pdo; - function log_error($errno, $errstr, $file, $line, $context) { + function log_error(int $errno, string $errstr, string $file, int $line, $context) { // separate PDO connection object is used for logging if (!$this->pdo) $this->pdo = Db::instance()->pdo_connect(); if ($this->pdo && get_schema_version() > 117) { - $owner_uid = $_SESSION["uid"] ?? null; - // limit context length, DOMDocument dumps entire XML in here sometimes, which may be huge $context = mb_substr($context, 0, 8192); @@ -34,10 +32,14 @@ class Logger_SQL { $errstr = UConverter::transcode($errstr, 'UTF-8', 'UTF-8'); $context = UConverter::transcode($context, 'UTF-8', 'UTF-8'); + // can't use $_SESSION["uid"] ?? null because what if its, for example, false? or zero? + // this would cause a PDOException on insert below + $owner_uid = !empty($_SESSION["uid"]) ? $_SESSION["uid"] : null; + $sth = $this->pdo->prepare("INSERT INTO ttrss_error_log (errno, errstr, filename, lineno, context, owner_uid, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())"); - $sth->execute([$errno, $errstr, $file, $line, $context, $owner_uid]); + $sth->execute([$errno, $errstr, $file, (int)$line, $context, $owner_uid]); return $sth->rowCount(); } diff --git a/classes/logger/stdout.php b/classes/logger/stdout.php index 4dac2e598..e906853ce 100644 --- a/classes/logger/stdout.php +++ b/classes/logger/stdout.php @@ -1,7 +1,7 @@ <?php -class Logger_Stdout { +class Logger_Stdout implements Logger_Adapter { - function log_error($errno, $errstr, $file, $line, $context) { + function log_error(int $errno, string $errstr, string $file, int $line, $context) { switch ($errno) { case E_ERROR: @@ -21,7 +21,7 @@ class Logger_Stdout { $priority = LOG_INFO; } - $errname = Logger::$errornames[$errno] . " ($errno)"; + $errname = Logger::ERROR_NAMES[$errno] . " ($errno)"; print "[EEE] $priority $errname ($file:$line) $errstr\n"; diff --git a/classes/logger/syslog.php b/classes/logger/syslog.php index f1e151548..3ad9858f3 100644 --- a/classes/logger/syslog.php +++ b/classes/logger/syslog.php @@ -1,7 +1,7 @@ <?php -class Logger_Syslog { +class Logger_Syslog implements Logger_Adapter { - function log_error($errno, $errstr, $file, $line, $context) { + function log_error(int $errno, string $errstr, string $file, int $line, $context) { switch ($errno) { case E_ERROR: @@ -21,7 +21,7 @@ class Logger_Syslog { $priority = LOG_INFO; } - $errname = Logger::$errornames[$errno] . " ($errno)"; + $errname = Logger::ERROR_NAMES[$errno] . " ($errno)"; syslog($priority, "[tt-rss] $errname ($file:$line) $errstr"); diff --git a/classes/mailer.php b/classes/mailer.php index 93f778210..a4270ba88 100644 --- a/classes/mailer.php +++ b/classes/mailer.php @@ -20,7 +20,7 @@ class Mailer { $to_combined = $to_name ? "$to_name <$to_address>" : $to_address; if (Config::get(Config::LOG_SENT_MAIL)) - Logger::get()->log(E_USER_NOTICE, "Sending mail from $from_combined to $to_combined [$subject]: $message"); + Logger::log(E_USER_NOTICE, "Sending mail from $from_combined to $to_combined [$subject]: $message"); // HOOK_SEND_MAIL plugin instructions: // 1. return 1 or true if mail is handled diff --git a/classes/opml.php b/classes/opml.php index cbc1269e3..6c7cab606 100644 --- a/classes/opml.php +++ b/classes/opml.php @@ -153,7 +153,7 @@ class OPML extends Handler_Protected { if ($include_settings) { $out .= "<outline text=\"tt-rss-prefs\" schema-version=\"".SCHEMA_VERSION."\">"; - $sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs WHERE + $sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs2 WHERE profile IS NULL AND owner_uid = ? ORDER BY pref_name"); $sth->execute([$owner_uid]); diff --git a/classes/pluginhost.php b/classes/pluginhost.php index b6f645a9c..348c67bab 100755 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -514,6 +514,14 @@ class PluginHost { } } + function get_array(Plugin $sender, string $name, array $default_value = []) { + $tmp = $this->get($sender, $name); + + if (!is_array($tmp)) $tmp = $default_value; + + return $tmp; + } + function get_all($sender) { $idx = get_class($sender); diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 755413f13..c7d4f1925 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -122,7 +122,7 @@ class Pref_Feeds extends Handler_Protected { $root['param'] = 0; $root['type'] = 'category'; - $enable_cats = get_pref('ENABLE_FEED_CATS'); + $enable_cats = get_pref(Prefs::ENABLE_FEED_CATS); if (clean($_REQUEST['mode'] ?? 0) == 2) { @@ -171,7 +171,7 @@ class Pref_Feeds extends Handler_Protected { ttrss_labels2 WHERE owner_uid = ? ORDER by caption"); $sth->execute([$_SESSION['uid']]); - if (get_pref('ENABLE_FEED_CATS')) { + if (get_pref(Prefs::ENABLE_FEED_CATS)) { $cat = $this->feedlist_init_cat(-2); } else { $cat['items'] = array(); @@ -527,11 +527,11 @@ class Pref_Feeds extends Handler_Protected { $row["icon"] = Feeds::_get_icon($feed_id); $local_update_intervals = $update_intervals; - $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref("DEFAULT_UPDATE_INTERVAL")]); + $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref(Prefs::DEFAULT_UPDATE_INTERVAL)]); if (Config::get(Config::FORCE_ARTICLE_PURGE) == 0) { $local_purge_intervals = $purge_intervals; - $default_purge_interval = get_pref("PURGE_OLD_DAYS"); + $default_purge_interval = get_pref(Prefs::PURGE_OLD_DAYS); if ($default_purge_interval > 0) $local_purge_intervals[0] .= " " . T_nsprintf('(%d day)', '(%d days)', $default_purge_interval, $default_purge_interval); @@ -546,7 +546,7 @@ class Pref_Feeds extends Handler_Protected { print json_encode([ "feed" => $row, "cats" => [ - "enabled" => get_pref('ENABLE_FEED_CATS'), + "enabled" => get_pref(Prefs::ENABLE_FEED_CATS), "select" => \Controls\select_feeds_cats("cat_id", $row["cat_id"]), ], "plugin_data" => $plugin_data, @@ -557,7 +557,7 @@ class Pref_Feeds extends Handler_Protected { ], "lang" => [ "enabled" => Config::get(Config::DB_TYPE) == "pgsql", - "default" => get_pref('DEFAULT_SEARCH_LANGUAGE'), + "default" => get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE), "all" => $this::get_ts_languages(), ] ]); @@ -576,10 +576,10 @@ class Pref_Feeds extends Handler_Protected { $feed_ids = clean($_REQUEST["ids"]); $local_update_intervals = $update_intervals; - $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref("DEFAULT_UPDATE_INTERVAL")]); + $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref(Prefs::DEFAULT_UPDATE_INTERVAL)]); $local_purge_intervals = $purge_intervals; - $default_purge_interval = get_pref("PURGE_OLD_DAYS"); + $default_purge_interval = get_pref(Prefs::PURGE_OLD_DAYS); if ($default_purge_interval > 0) $local_purge_intervals[0] .= " " . T_sprintf("(%d days)", $default_purge_interval); @@ -604,7 +604,7 @@ class Pref_Feeds extends Handler_Protected { <div dojoType="dijit.layout.TabContainer" style="height : 450px"> <div dojoType="dijit.layout.ContentPane" title="<?= __('General') ?>"> <section> - <?php if (get_pref('ENABLE_FEED_CATS')) { ?> + <?php if (get_pref(Prefs::ENABLE_FEED_CATS)) { ?> <fieldset> <label><?= __('Place in category:') ?></label> <?= \Controls\select_feeds_cats("cat_id", null, ['disabled' => '1']) ?> @@ -830,7 +830,7 @@ class Pref_Feeds extends Handler_Protected { break; case "cat_id": - if (get_pref('ENABLE_FEED_CATS')) { + if (get_pref(Prefs::ENABLE_FEED_CATS)) { if ($cat_id) { $qpart = "cat_id = " . $this->pdo->quote($cat_id); } else { @@ -946,7 +946,7 @@ class Pref_Feeds extends Handler_Protected { </div> </div> - <?php if (get_pref('ENABLE_FEED_CATS')) { ?> + <?php if (get_pref(Prefs::ENABLE_FEED_CATS)) { ?> <div dojoType="fox.form.DropDownButton"> <span><?= __('Categories') ?></span> <div dojoType="dijit.Menu" style="display: none"> @@ -1237,7 +1237,7 @@ class Pref_Feeds extends Handler_Protected { function batchSubscribe() { print json_encode([ - "enable_cats" => (int)get_pref('ENABLE_FEED_CATS'), + "enable_cats" => (int)get_pref(Prefs::ENABLE_FEED_CATS), "cat_select" => \Controls\select_feeds_cats("cat") ]); } diff --git a/classes/pref/filters.php b/classes/pref/filters.php index fda4a6513..a6ea9f982 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -848,7 +848,7 @@ class Pref_Filters extends Handler_Protected { } } - if (get_pref('ENABLE_FEED_CATS')) { + if (get_pref(Prefs::ENABLE_FEED_CATS)) { if (!$root_id) $root_id = null; diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index 0d0dcadbc..ba63d76b3 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -6,7 +6,6 @@ class Pref_Prefs extends Handler_Protected { private $pref_item_map = []; private $pref_help_bottom = []; private $pref_blacklist = []; - private $profile_blacklist = []; function csrf_ignore($method) { $csrf_ignored = array("index", "updateself", "otpqrcode"); @@ -19,106 +18,99 @@ class Pref_Prefs extends Handler_Protected { $this->pref_item_map = [ __('General') => [ - 'USER_LANGUAGE', - 'USER_TIMEZONE', + Prefs::USER_LANGUAGE, + Prefs::USER_TIMEZONE, 'BLOCK_SEPARATOR', - 'USER_CSS_THEME', + Prefs::USER_CSS_THEME, 'BLOCK_SEPARATOR', - 'ENABLE_API_ACCESS', + Prefs::ENABLE_API_ACCESS, ], __('Feeds') => [ - 'DEFAULT_UPDATE_INTERVAL', - 'FRESH_ARTICLE_MAX_AGE', - 'DEFAULT_SEARCH_LANGUAGE', + Prefs::DEFAULT_UPDATE_INTERVAL, + Prefs::FRESH_ARTICLE_MAX_AGE, + Prefs::DEFAULT_SEARCH_LANGUAGE, 'BLOCK_SEPARATOR', - 'ENABLE_FEED_CATS', + Prefs::ENABLE_FEED_CATS, 'BLOCK_SEPARATOR', - 'CONFIRM_FEED_CATCHUP', - 'ON_CATCHUP_SHOW_NEXT_FEED', + Prefs::CONFIRM_FEED_CATCHUP, + Prefs::ON_CATCHUP_SHOW_NEXT_FEED, 'BLOCK_SEPARATOR', - 'HIDE_READ_FEEDS', - 'HIDE_READ_SHOWS_SPECIAL', + Prefs::HIDE_READ_FEEDS, + Prefs::HIDE_READ_SHOWS_SPECIAL, ], __('Articles') => [ - 'PURGE_OLD_DAYS', - 'PURGE_UNREAD_ARTICLES', + Prefs::PURGE_OLD_DAYS, + Prefs::PURGE_UNREAD_ARTICLES, 'BLOCK_SEPARATOR', - 'COMBINED_DISPLAY_MODE', - 'CDM_EXPANDED', + Prefs::COMBINED_DISPLAY_MODE, + Prefs::CDM_EXPANDED, 'BLOCK_SEPARATOR', - 'CDM_AUTO_CATCHUP', - 'VFEED_GROUP_BY_FEED', + Prefs::CDM_AUTO_CATCHUP, + Prefs::VFEED_GROUP_BY_FEED, 'BLOCK_SEPARATOR', - 'SHOW_CONTENT_PREVIEW', - 'STRIP_IMAGES', + Prefs::SHOW_CONTENT_PREVIEW, + Prefs::STRIP_IMAGES, ], __('Digest') => [ - 'DIGEST_ENABLE', - 'DIGEST_CATCHUP', - 'DIGEST_PREFERRED_TIME', + Prefs::DIGEST_ENABLE, + Prefs::DIGEST_CATCHUP, + Prefs::DIGEST_PREFERRED_TIME, ], __('Advanced') => [ - 'BLACKLISTED_TAGS', + Prefs::BLACKLISTED_TAGS, 'BLOCK_SEPARATOR', - 'LONG_DATE_FORMAT', - 'SHORT_DATE_FORMAT', + Prefs::LONG_DATE_FORMAT, + Prefs::SHORT_DATE_FORMAT, 'BLOCK_SEPARATOR', - 'SSL_CERT_SERIAL', - ] + Prefs::SSL_CERT_SERIAL, + 'BLOCK_SEPARATOR', + Prefs::HEADLINES_NO_DISTINCT, + ], + __('Debugging') => [ + Prefs::DEBUG_HEADLINE_IDS, + ], ]; $this->pref_help_bottom = [ - "BLACKLISTED_TAGS" => __("Never apply these tags automatically (comma-separated list)."), + Prefs::BLACKLISTED_TAGS => __("Never apply these tags automatically (comma-separated list)."), ]; $this->pref_help = [ - "ALLOW_DUPLICATE_POSTS" => array(__("Allow duplicate articles"), ""), - "BLACKLISTED_TAGS" => array(__("Blacklisted tags"), ""), - "DEFAULT_SEARCH_LANGUAGE" => array(__("Default language"), __("Used for full-text search")), - "CDM_AUTO_CATCHUP" => array(__("Mark read on scroll"), __("Mark articles as read as you scroll past them")), - "CDM_EXPANDED" => array(__("Always expand articles")), - "COMBINED_DISPLAY_MODE" => array(__("Combined mode"), __("Show flat list of articles instead of separate panels")), - "CONFIRM_FEED_CATCHUP" => array(__("Confirm marking feeds as read")), - "DEFAULT_ARTICLE_LIMIT" => array(__("Amount of articles to display at once")), - "DEFAULT_UPDATE_INTERVAL" => array(__("Default update interval")), - "DIGEST_CATCHUP" => array(__("Mark sent articles as read")), - "DIGEST_ENABLE" => array(__("Enable digest"), __("Send daily digest of new (and unread) headlines to your e-mail address")), - "DIGEST_PREFERRED_TIME" => array(__("Try to send around this time"), __("Time in UTC")), - "ENABLE_API_ACCESS" => array(__("Enable API"), __("Allows accessing this account through the API")), - "ENABLE_FEED_CATS" => array(__("Enable categories")), - "FEEDS_SORT_BY_UNREAD" => array(__("Sort feeds by unread articles count"), ""), - "FRESH_ARTICLE_MAX_AGE" => array(__("Maximum age of fresh articles"), "<strong>" . __("hours") . "</strong>"), - "HIDE_READ_FEEDS" => array(__("Hide read feeds")), - "HIDE_READ_SHOWS_SPECIAL" => array(__("Always show special feeds"), __("While hiding read feeds")), - "LONG_DATE_FORMAT" => array(__("Long date format"), __("Syntax is identical to PHP <a href='http://php.net/manual/function.date.php'>date()</a> function.")), - "ON_CATCHUP_SHOW_NEXT_FEED" => array(__("Automatically show next feed"), __("After marking one as read")), - "PURGE_OLD_DAYS" => array(__("Purge articles older than"), __("<strong>days</strong> (0 disables)")), - "PURGE_UNREAD_ARTICLES" => array(__("Purge unread articles")), - "REVERSE_HEADLINES" => array(__("Reverse headline order (oldest first)")), - "SHORT_DATE_FORMAT" => array(__("Short date format")), - "SHOW_CONTENT_PREVIEW" => array(__("Show content preview in headlines")), - "SORT_HEADLINES_BY_FEED_DATE" => array(__("Sort headlines by feed date"), __("Use feed-specified date to sort headlines instead of local import date.")), - "SSL_CERT_SERIAL" => array(__("SSL client certificate")), - "STRIP_IMAGES" => array(__("Do not embed media")), - "STRIP_UNSAFE_TAGS" => array(__("Strip unsafe tags from articles"), __("Strip all but most common HTML tags when reading articles.")), - "USER_STYLESHEET" => array(__("Customize stylesheet")), - "USER_TIMEZONE" => array(__("Time zone")), - "VFEED_GROUP_BY_FEED" => array(__("Group by feed"), __("Group multiple-feed output by originating feed")), - "USER_LANGUAGE" => array(__("Language")), - "USER_CSS_THEME" => array(__("Theme")) + Prefs::BLACKLISTED_TAGS => array(__("Blacklisted tags"), ""), + Prefs::DEFAULT_SEARCH_LANGUAGE => array(__("Default language"), __("Used for full-text search")), + Prefs::CDM_AUTO_CATCHUP => array(__("Mark read on scroll"), __("Mark articles as read as you scroll past them")), + Prefs::CDM_EXPANDED => array(__("Always expand articles")), + Prefs::COMBINED_DISPLAY_MODE => array(__("Combined mode"), __("Show flat list of articles instead of separate panels")), + Prefs::CONFIRM_FEED_CATCHUP => array(__("Confirm marking feeds as read")), + Prefs::DEFAULT_UPDATE_INTERVAL => array(__("Default update interval")), + Prefs::DIGEST_CATCHUP => array(__("Mark sent articles as read")), + Prefs::DIGEST_ENABLE => array(__("Enable digest"), __("Send daily digest of new (and unread) headlines to your e-mail address")), + Prefs::DIGEST_PREFERRED_TIME => array(__("Try to send around this time"), __("Time in UTC")), + Prefs::ENABLE_API_ACCESS => array(__("Enable API"), __("Allows accessing this account through the API")), + Prefs::ENABLE_FEED_CATS => array(__("Enable categories")), + Prefs::FRESH_ARTICLE_MAX_AGE => array(__("Maximum age of fresh articles"), "<strong>" . __("hours") . "</strong>"), + Prefs::HIDE_READ_FEEDS => array(__("Hide read feeds")), + Prefs::HIDE_READ_SHOWS_SPECIAL => array(__("Always show special feeds"), __("While hiding read feeds")), + Prefs::LONG_DATE_FORMAT => array(__("Long date format"), __("Syntax is identical to PHP <a href='http://php.net/manual/function.date.php'>date()</a> function.")), + Prefs::ON_CATCHUP_SHOW_NEXT_FEED => array(__("Automatically show next feed"), __("After marking one as read")), + Prefs::PURGE_OLD_DAYS => array(__("Purge articles older than"), __("<strong>days</strong> (0 disables)")), + Prefs::PURGE_UNREAD_ARTICLES => array(__("Purge unread articles")), + Prefs::SHORT_DATE_FORMAT => array(__("Short date format")), + Prefs::SHOW_CONTENT_PREVIEW => array(__("Show content preview in headlines")), + Prefs::SSL_CERT_SERIAL => array(__("SSL client certificate")), + Prefs::STRIP_IMAGES => array(__("Do not embed media")), + Prefs::USER_TIMEZONE => array(__("Time zone")), + Prefs::VFEED_GROUP_BY_FEED => array(__("Group by feed"), __("Group multiple-feed output by originating feed")), + Prefs::USER_LANGUAGE => array(__("Language")), + Prefs::USER_CSS_THEME => array(__("Theme")), + Prefs::HEADLINES_NO_DISTINCT => array(__("Don't enforce DISTINCT headlines"), __("May produce duplicate entries")), + Prefs::DEBUG_HEADLINE_IDS => array(__("Show article and feed IDs"), __("In the headlines buffer")), ]; - $this->pref_blacklist = ["ALLOW_DUPLICATE_POSTS", "STRIP_UNSAFE_TAGS", "REVERSE_HEADLINES", - "SORT_HEADLINES_BY_FEED_DATE", "DEFAULT_ARTICLE_LIMIT", - "FEEDS_SORT_BY_UNREAD", "USER_STYLESHEET"]; - - /* "FEEDS_SORT_BY_UNREAD", "HIDE_READ_FEEDS", "REVERSE_HEADLINES" */ - - $this->profile_blacklist = ["ALLOW_DUPLICATE_POSTS", "PURGE_OLD_DAYS", - "PURGE_UNREAD_ARTICLES", "DIGEST_ENABLE", "DIGEST_CATCHUP", - "BLACKLISTED_TAGS", "ENABLE_API_ACCESS", "UPDATE_POST_ON_CHECKSUM_CHANGE", - "DEFAULT_UPDATE_INTERVAL", "USER_TIMEZONE", "SORT_HEADLINES_BY_FEED_DATE", - "SSL_CERT_SERIAL", "DIGEST_PREFERRED_TIME"]; + // hidden in the main prefs UI (use to hide things that have description set above) + $this->pref_blacklist = [ + // + ]; } function changepassword() { @@ -181,31 +173,33 @@ class Pref_Prefs extends Handler_Protected { $value = $_POST[$pref_name]; switch ($pref_name) { - case 'DIGEST_PREFERRED_TIME': - if (get_pref('DIGEST_PREFERRED_TIME') != $value) { + case Prefs::DIGEST_PREFERRED_TIME: + if (get_pref(Prefs::DIGEST_PREFERRED_TIME) != $value) { $sth = $this->pdo->prepare("UPDATE ttrss_users SET - last_digest_sent = NULL WHERE id = ?"); + last_digest_sent = NULL WHERE id = ?"); $sth->execute([$_SESSION['uid']]); } break; - case 'USER_LANGUAGE': + case Prefs::USER_LANGUAGE: if (!$need_reload) $need_reload = $_SESSION["language"] != $value; break; - case 'USER_CSS_THEME': + case Prefs::USER_CSS_THEME: if (!$need_reload) $need_reload = get_pref($pref_name) != $value; break; - case 'BLACKLISTED_TAGS': + case Prefs::BLACKLISTED_TAGS: $cats = FeedItem_Common::normalize_categories(explode(",", $value)); asort($cats); $value = implode(", ", $cats); break; } - set_pref($pref_name, $value); + if (Prefs::is_valid($pref_name)) { + Prefs::set($pref_name, $value, $_SESSION["uid"], $_SESSION["profile"] ?? null); + } } if ($need_reload) { @@ -260,17 +254,9 @@ class Pref_Prefs extends Handler_Protected { } function resetconfig() { + Prefs::reset($_SESSION["uid"], $_SESSION["profile"]); - $_SESSION["prefs_op_result"] = "reset-to-defaults"; - - $sth = $this->pdo->prepare("DELETE FROM ttrss_user_prefs - WHERE (profile = :profile OR (:profile IS NULL AND profile IS NULL)) - AND owner_uid = :uid"); - $sth->execute([":profile" => $_SESSION['profile'], ":uid" => $_SESSION['uid']]); - - $this->_init_user_prefs($_SESSION["uid"], $_SESSION["profile"]); - - echo __("Your preferences are now set to default values."); + print "PREFS_NEED_RELOAD"; } private function index_auth_personal() { @@ -568,35 +554,18 @@ class Pref_Prefs extends Handler_Protected { if ($profile) { print_notice(__("Some preferences are only available in default profile.")); - $this->_init_user_prefs($_SESSION["uid"], $profile); - } else { - $this->_init_user_prefs($_SESSION["uid"]); } $prefs_available = []; - - $sth = $this->pdo->prepare("SELECT DISTINCT - ttrss_user_prefs.pref_name,value,type_name, - ttrss_prefs_sections.order_id, - def_value,section_id - FROM ttrss_prefs,ttrss_prefs_types,ttrss_prefs_sections,ttrss_user_prefs - WHERE type_id = ttrss_prefs_types.id AND - (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND - section_id = ttrss_prefs_sections.id AND - ttrss_user_prefs.pref_name = ttrss_prefs.pref_name AND - owner_uid = :uid - ORDER BY ttrss_prefs_sections.order_id,pref_name"); - $sth->execute([":uid" => $_SESSION['uid'], ":profile" => $profile]); - $listed_boolean_prefs = []; - while ($line = $sth->fetch()) { + foreach (Prefs::get_all($_SESSION["uid"], $profile) as $line) { if (in_array($line["pref_name"], $this->pref_blacklist)) { continue; } - if ($profile && in_array($line["pref_name"], $this->profile_blacklist)) { + if ($profile && in_array($line["pref_name"], Prefs::_PROFILE_BLACKLIST)) { continue; } @@ -607,7 +576,7 @@ class Pref_Prefs extends Handler_Protected { continue; $prefs_available[$pref_name] = [ - 'type_name' => $line["type_name"], + 'type_hint' => $line['type_hint'], 'value' => $line['value'], 'help_text' => $this->_get_help_text($pref_name), 'short_desc' => $short_desc @@ -640,7 +609,7 @@ class Pref_Prefs extends Handler_Protected { print "</label>"; $value = $item['value']; - $type_name = $item['type_name']; + $type_hint = $item['type_hint']; if ($pref_name == "USER_LANGUAGE") { print \Controls\select_hash($pref_name, $value, get_translations(), @@ -701,7 +670,7 @@ class Pref_Prefs extends Handler_Protected { print \Controls\select_tag($pref_name, $value, Pref_Feeds::get_ts_languages()); - } else if ($type_name == "bool") { + } else if ($type_hint == Config::T_BOOL) { array_push($listed_boolean_prefs, $pref_name); @@ -726,7 +695,7 @@ class Pref_Prefs extends Handler_Protected { $attributes = ["required" => true]; } - if ($type_name == 'integer') + if ($type_hint == Config::T_INT) print \Controls\number_spinner_tag($pref_name, $value, $attributes); else print \Controls\input_tag($pref_name, $value, "text", $attributes); @@ -757,7 +726,7 @@ class Pref_Prefs extends Handler_Protected { $item['help_text'] .= ". " . T_sprintf("Current server time: %s", date("H:i")); } else { - $regexp = ($type_name == 'integer') ? 'regexp="^\d*$"' : ''; + $regexp = ($type_hint == Config::T_INT) ? 'regexp="^\d*$"' : ''; print "<input dojoType=\"dijit.form.ValidationTextBox\" $regexp name=\"$pref_name\" value=\"$value\">"; } @@ -863,7 +832,7 @@ class Pref_Prefs extends Handler_Protected { private function index_plugins_user() { $system_enabled = array_map("trim", explode(",", (string)Config::get(Config::PLUGINS))); - $user_enabled = array_map("trim", explode(",", get_pref("_ENABLED_PLUGINS"))); + $user_enabled = array_map("trim", explode(",", get_pref(Prefs::_ENABLED_PLUGINS))); $tmppluginhost = new PluginHost(); $tmppluginhost->load_all($tmppluginhost::KIND_ALL, $_SESSION["uid"], true); @@ -1164,7 +1133,7 @@ class Pref_Prefs extends Handler_Protected { else $plugins = ""; - set_pref("_ENABLED_PLUGINS", $plugins); + set_pref(Prefs::_ENABLED_PLUGINS, $plugins); } function clearplugindata() { @@ -1174,7 +1143,7 @@ class Pref_Prefs extends Handler_Protected { } function customizeCSS() { - $value = get_pref("USER_STYLESHEET"); + $value = get_pref(Prefs::USER_STYLESHEET); $value = str_replace("<br/>", "\n", $value); print json_encode(["value" => $value]); @@ -1219,14 +1188,6 @@ class Pref_Prefs extends Handler_Protected { $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles WHERE title = ? AND owner_uid = ?"); $sth->execute([$title, $_SESSION['uid']]); - - if ($row = $sth->fetch()) { - $profile_id = $row['id']; - - if ($profile_id) { - Pref_Prefs::_init_user_prefs($_SESSION["uid"], $profile_id); - } - } } $this->pdo->commit(); @@ -1367,57 +1328,4 @@ class Pref_Prefs extends Handler_Protected { $this->appPasswordList(); } - - static function _init_user_prefs($uid, $profile = false) { - - if (get_schema_version() < 63) $profile_qpart = ""; - - $pdo = Db::pdo(); - $in_nested_tr = false; - - try { - $pdo->beginTransaction(); - } catch (Exception $e) { - $in_nested_tr = true; - } - - $sth = $pdo->query("SELECT pref_name,def_value FROM ttrss_prefs"); - - if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null; - - $u_sth = $pdo->prepare("SELECT pref_name - FROM ttrss_user_prefs WHERE owner_uid = :uid AND - (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); - $u_sth->execute([':uid' => $uid, ':profile' => $profile]); - - $active_prefs = array(); - - while ($line = $u_sth->fetch()) { - array_push($active_prefs, $line["pref_name"]); - } - - while ($line = $sth->fetch()) { - if (array_search($line["pref_name"], $active_prefs) === false) { -// print "adding " . $line["pref_name"] . "<br>"; - - if (get_schema_version() < 63) { - $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs - (owner_uid,pref_name,value) VALUES - (?, ?, ?)"); - $i_sth->execute([$uid, $line["pref_name"], $line["def_value"]]); - - } else { - $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs - (owner_uid,pref_name,value, profile) VALUES - (?, ?, ?, ?)"); - $i_sth->execute([$uid, $line["pref_name"], $line["def_value"], $profile]); - } - - } - } - - if (!$in_nested_tr) $pdo->commit(); - - } - } diff --git a/classes/pref/system.php b/classes/pref/system.php index 67f7133c6..85635e753 100644 --- a/classes/pref/system.php +++ b/classes/pref/system.php @@ -129,7 +129,7 @@ class Pref_System extends Handler_Administrative { ?> <tr> <td class='errno'> - <?= Logger::$errornames[$line["errno"]] . " (" . $line["errno"] . ")" ?> + <?= Logger::ERROR_NAMES[$line["errno"]] . " (" . $line["errno"] . ")" ?> </td> <td class='filename'><?= $line["filename"] . ":" . $line["lineno"] ?></td> <td class='errstr'><?= $line["errstr"] . "\n" . $line["context"] ?></td> diff --git a/classes/prefs.php b/classes/prefs.php new file mode 100644 index 000000000..703246e9c --- /dev/null +++ b/classes/prefs.php @@ -0,0 +1,407 @@ +<?php +class Prefs { + // (this is the database-backed version of Config.php) + + const PURGE_OLD_DAYS = "PURGE_OLD_DAYS"; + const DEFAULT_UPDATE_INTERVAL = "DEFAULT_UPDATE_INTERVAL"; + //const DEFAULT_ARTICLE_LIMIT = "DEFAULT_ARTICLE_LIMIT"; + //const ALLOW_DUPLICATE_POSTS = "ALLOW_DUPLICATE_POSTS"; + const ENABLE_FEED_CATS = "ENABLE_FEED_CATS"; + const SHOW_CONTENT_PREVIEW = "SHOW_CONTENT_PREVIEW"; + const SHORT_DATE_FORMAT = "SHORT_DATE_FORMAT"; + const LONG_DATE_FORMAT = "LONG_DATE_FORMAT"; + const COMBINED_DISPLAY_MODE = "COMBINED_DISPLAY_MODE"; + const HIDE_READ_FEEDS = "HIDE_READ_FEEDS"; + const ON_CATCHUP_SHOW_NEXT_FEED = "ON_CATCHUP_SHOW_NEXT_FEED"; + const FEEDS_SORT_BY_UNREAD = "FEEDS_SORT_BY_UNREAD"; + const REVERSE_HEADLINES = "REVERSE_HEADLINES"; + const DIGEST_ENABLE = "DIGEST_ENABLE"; + const CONFIRM_FEED_CATCHUP = "CONFIRM_FEED_CATCHUP"; + const CDM_AUTO_CATCHUP = "CDM_AUTO_CATCHUP"; + const _DEFAULT_VIEW_MODE = "_DEFAULT_VIEW_MODE"; + const _DEFAULT_VIEW_LIMIT = "_DEFAULT_VIEW_LIMIT"; + //const _PREFS_ACTIVE_TAB = "_PREFS_ACTIVE_TAB"; + //const STRIP_UNSAFE_TAGS = "STRIP_UNSAFE_TAGS"; + const BLACKLISTED_TAGS = "BLACKLISTED_TAGS"; + const FRESH_ARTICLE_MAX_AGE = "FRESH_ARTICLE_MAX_AGE"; + const DIGEST_CATCHUP = "DIGEST_CATCHUP"; + const CDM_EXPANDED = "CDM_EXPANDED"; + const PURGE_UNREAD_ARTICLES = "PURGE_UNREAD_ARTICLES"; + const HIDE_READ_SHOWS_SPECIAL = "HIDE_READ_SHOWS_SPECIAL"; + const VFEED_GROUP_BY_FEED = "VFEED_GROUP_BY_FEED"; + const STRIP_IMAGES = "STRIP_IMAGES"; + const _DEFAULT_VIEW_ORDER_BY = "_DEFAULT_VIEW_ORDER_BY"; + const ENABLE_API_ACCESS = "ENABLE_API_ACCESS"; + //const _COLLAPSED_SPECIAL = "_COLLAPSED_SPECIAL"; + //const _COLLAPSED_LABELS = "_COLLAPSED_LABELS"; + //const _COLLAPSED_UNCAT = "_COLLAPSED_UNCAT"; + //const _COLLAPSED_FEEDLIST = "_COLLAPSED_FEEDLIST"; + //const _MOBILE_ENABLE_CATS = "_MOBILE_ENABLE_CATS"; + //const _MOBILE_SHOW_IMAGES = "_MOBILE_SHOW_IMAGES"; + //const _MOBILE_HIDE_READ = "_MOBILE_HIDE_READ"; + //const _MOBILE_SORT_FEEDS_UNREAD = "_MOBILE_SORT_FEEDS_UNREAD"; + //const _MOBILE_BROWSE_CATS = "_MOBILE_BROWSE_CATS"; + //const _THEME_ID = "_THEME_ID"; + const USER_TIMEZONE = "USER_TIMEZONE"; + const USER_STYLESHEET = "USER_STYLESHEET"; + //const SORT_HEADLINES_BY_FEED_DATE = "SORT_HEADLINES_BY_FEED_DATE"; + const SSL_CERT_SERIAL = "SSL_CERT_SERIAL"; + const DIGEST_PREFERRED_TIME = "DIGEST_PREFERRED_TIME"; + //const _PREFS_SHOW_EMPTY_CATS = "_PREFS_SHOW_EMPTY_CATS"; + const _DEFAULT_INCLUDE_CHILDREN = "_DEFAULT_INCLUDE_CHILDREN"; + //const AUTO_ASSIGN_LABELS = "AUTO_ASSIGN_LABELS"; + const _ENABLED_PLUGINS = "_ENABLED_PLUGINS"; + //const _MOBILE_REVERSE_HEADLINES = "_MOBILE_REVERSE_HEADLINES"; + const USER_CSS_THEME = "USER_CSS_THEME"; + const USER_LANGUAGE = "USER_LANGUAGE"; + const DEFAULT_SEARCH_LANGUAGE = "DEFAULT_SEARCH_LANGUAGE"; + const _PREFS_MIGRATED = "_PREFS_MIGRATED"; + const HEADLINES_NO_DISTINCT = "HEADLINES_NO_DISTINCT"; + const DEBUG_HEADLINE_IDS = "DEBUG_HEADLINE_IDS"; + + private const _DEFAULTS = [ + Prefs::PURGE_OLD_DAYS => [ 60, Config::T_INT ], + Prefs::DEFAULT_UPDATE_INTERVAL => [ 30, Config::T_INT ], + //Prefs::DEFAULT_ARTICLE_LIMIT => [ 30, Config::T_INT ], + //Prefs::ALLOW_DUPLICATE_POSTS => [ false, Config::T_BOOL ], + Prefs::ENABLE_FEED_CATS => [ true, Config::T_BOOL ], + Prefs::SHOW_CONTENT_PREVIEW => [ true, Config::T_BOOL ], + Prefs::SHORT_DATE_FORMAT => [ "M d, G:i", Config::T_STRING ], + Prefs::LONG_DATE_FORMAT => [ "D, M d Y - G:i", Config::T_STRING ], + Prefs::COMBINED_DISPLAY_MODE => [ true, Config::T_BOOL ], + Prefs::HIDE_READ_FEEDS => [ false, Config::T_BOOL ], + Prefs::ON_CATCHUP_SHOW_NEXT_FEED => [ false, Config::T_BOOL ], + Prefs::FEEDS_SORT_BY_UNREAD => [ false, Config::T_BOOL ], + Prefs::REVERSE_HEADLINES => [ false, Config::T_BOOL ], + Prefs::DIGEST_ENABLE => [ false, Config::T_BOOL ], + Prefs::CONFIRM_FEED_CATCHUP => [ true, Config::T_BOOL ], + Prefs::CDM_AUTO_CATCHUP => [ false, Config::T_BOOL ], + Prefs::_DEFAULT_VIEW_MODE => [ "adaptive", Config::T_STRING ], + Prefs::_DEFAULT_VIEW_LIMIT => [ 30, Config::T_INT ], + //Prefs::_PREFS_ACTIVE_TAB => [ "", Config::T_STRING ], + //Prefs::STRIP_UNSAFE_TAGS => [ true, Config::T_BOOL ], + Prefs::BLACKLISTED_TAGS => [ 'main, generic, misc, uncategorized, blog, blogroll, general, news', Config::T_STRING ], + Prefs::FRESH_ARTICLE_MAX_AGE => [ 24, Config::T_INT ], + Prefs::DIGEST_CATCHUP => [ false, Config::T_BOOL ], + Prefs::CDM_EXPANDED => [ true, Config::T_BOOL ], + Prefs::PURGE_UNREAD_ARTICLES => [ true, Config::T_BOOL ], + Prefs::HIDE_READ_SHOWS_SPECIAL => [ true, Config::T_BOOL ], + Prefs::VFEED_GROUP_BY_FEED => [ false, Config::T_BOOL ], + Prefs::STRIP_IMAGES => [ false, Config::T_BOOL ], + Prefs::_DEFAULT_VIEW_ORDER_BY => [ "default", Config::T_STRING ], + Prefs::ENABLE_API_ACCESS => [ false, Config::T_BOOL ], + //Prefs::_COLLAPSED_SPECIAL => [ false, Config::T_BOOL ], + //Prefs::_COLLAPSED_LABELS => [ false, Config::T_BOOL ], + //Prefs::_COLLAPSED_UNCAT => [ false, Config::T_BOOL ], + //Prefs::_COLLAPSED_FEEDLIST => [ false, Config::T_BOOL ], + //Prefs::_MOBILE_ENABLE_CATS => [ false, Config::T_BOOL ], + //Prefs::_MOBILE_SHOW_IMAGES => [ false, Config::T_BOOL ], + //Prefs::_MOBILE_HIDE_READ => [ false, Config::T_BOOL ], + //Prefs::_MOBILE_SORT_FEEDS_UNREAD => [ false, Config::T_BOOL ], + //Prefs::_MOBILE_BROWSE_CATS => [ true, Config::T_BOOL ], + //Prefs::_THEME_ID => [ 0, Config::T_BOOL ], + Prefs::USER_TIMEZONE => [ "Automatic", Config::T_STRING ], + Prefs::USER_STYLESHEET => [ "", Config::T_STRING ], + //Prefs::SORT_HEADLINES_BY_FEED_DATE => [ false, Config::T_BOOL ], + Prefs::SSL_CERT_SERIAL => [ "", Config::T_STRING ], + Prefs::DIGEST_PREFERRED_TIME => [ "00:00", Config::T_STRING ], + //Prefs::_PREFS_SHOW_EMPTY_CATS => [ false, Config::T_BOOL ], + Prefs::_DEFAULT_INCLUDE_CHILDREN => [ false, Config::T_BOOL ], + //Prefs::AUTO_ASSIGN_LABELS => [ false, Config::T_BOOL ], + Prefs::_ENABLED_PLUGINS => [ "", Config::T_STRING ], + //Prefs::_MOBILE_REVERSE_HEADLINES => [ false, Config::T_BOOL ], + Prefs::USER_CSS_THEME => [ "" , Config::T_STRING ], + Prefs::USER_LANGUAGE => [ "" , Config::T_STRING ], + Prefs::DEFAULT_SEARCH_LANGUAGE => [ "" , Config::T_STRING ], + Prefs::_PREFS_MIGRATED => [ false, Config::T_BOOL ], + Prefs::HEADLINES_NO_DISTINCT => [ false, Config::T_BOOL ], + Prefs::DEBUG_HEADLINE_IDS => [ false, Config::T_BOOL ], + ]; + + const _PROFILE_BLACKLIST = [ + //Prefs::ALLOW_DUPLICATE_POSTS, + Prefs::PURGE_OLD_DAYS, + Prefs::PURGE_UNREAD_ARTICLES, + Prefs::DIGEST_ENABLE, + Prefs::DIGEST_CATCHUP, + Prefs::BLACKLISTED_TAGS, + Prefs::ENABLE_API_ACCESS, + //Prefs::UPDATE_POST_ON_CHECKSUM_CHANGE, + Prefs::DEFAULT_UPDATE_INTERVAL, + Prefs::USER_TIMEZONE, + //Prefs::SORT_HEADLINES_BY_FEED_DATE, + Prefs::SSL_CERT_SERIAL, + Prefs::DIGEST_PREFERRED_TIME, + Prefs::_PREFS_MIGRATED + ]; + + private static $instance; + private $cache = []; + + /** @var PDO */ + private $pdo; + + public static function get_instance() : Prefs { + if (self::$instance == null) + self::$instance = new self(); + + return self::$instance; + } + + static function is_valid(string $pref_name) { + return isset(self::_DEFAULTS[$pref_name]); + } + + static function get_default(string $pref_name) { + if (self::is_valid($pref_name)) + return self::_DEFAULTS[$pref_name][0]; + else + return null; + } + + function __construct() { + $this->pdo = Db::pdo(); + + if (!empty($_SESSION["uid"])) { + $owner_uid = (int) $_SESSION["uid"]; + $profile_id = $_SESSION["profile"] ?? null; + + $this->cache_all($owner_uid, $profile_id); + $this->migrate($owner_uid, $profile_id); + }; + } + + private function __clone() { + // + } + + static function get_all(int $owner_uid, int $profile_id = null) { + return self::get_instance()->_get_all($owner_uid, $profile_id); + } + + private function _get_all(int $owner_uid, int $profile_id = null) { + $rv = []; + + $ref = new ReflectionClass(get_class($this)); + + foreach ($ref->getConstants() as $const => $cvalue) { + if (isset($this::_DEFAULTS[$const])) { + list ($def_val, $type_hint) = $this::_DEFAULTS[$const]; + + array_push($rv, [ + "pref_name" => $const, + "value" => $this->_get($const, $owner_uid, $profile_id), + "type_hint" => $type_hint, + ]); + } + } + + return $rv; + } + + private function cache_all(int $owner_uid, $profile_id = null) { + if (!$profile_id) $profile_id = null; + + // fill cache with defaults + $ref = new ReflectionClass(get_class($this)); + foreach ($ref->getConstants() as $const => $cvalue) { + if (isset($this::_DEFAULTS[$const])) { + list ($def_val, $type_hint) = $this::_DEFAULTS[$const]; + + $this->_set_cache($const, $def_val, $owner_uid, $profile_id); + } + } + + if (get_schema_version() >= 141) { + // fill in any overrides from the database + $sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs2 + WHERE owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + + $sth->execute(["uid" => $owner_uid, "profile" => $profile_id]); + + while ($row = $sth->fetch()) { + $this->_set_cache($row["pref_name"], $row["value"], $owner_uid, $profile_id); + } + } + } + + static function get(string $pref_name, int $owner_uid, int $profile_id = null) { + return self::get_instance()->_get($pref_name, $owner_uid, $profile_id); + } + + private function _get(string $pref_name, int $owner_uid, int $profile_id = null) { + if (isset(self::_DEFAULTS[$pref_name])) { + if (!$profile_id || in_array($pref_name, self::_PROFILE_BLACKLIST)) $profile_id = null; + + list ($def_val, $type_hint) = self::_DEFAULTS[$pref_name]; + + $cached_value = $this->_get_cache($pref_name, $owner_uid, $profile_id); + + if ($this->_is_cached($pref_name, $owner_uid, $profile_id)) { + $cached_value = $this->_get_cache($pref_name, $owner_uid, $profile_id); + return Config::cast_to($cached_value, $type_hint); + } else if (get_schema_version() >= 141) { + $sth = $this->pdo->prepare("SELECT value FROM ttrss_user_prefs2 + WHERE pref_name = :name AND owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + + $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]); + + if ($row = $sth->fetch(PDO::FETCH_ASSOC)) { + $this->_set_cache($pref_name, $row["value"], $owner_uid, $profile_id); + + return Config::cast_to($row["value"], $type_hint); + } else { + $this->_set_cache($pref_name, $def_val, $owner_uid, $profile_id); + + return $def_val; + } + } else { + return Config::cast_to($def_val, $type_hint); + + } + } else { + user_error("Attempt to get invalid preference key: $pref_name (UID: $owner_uid, profile: $profile_id)", E_USER_WARNING); + } + + return null; + } + + private function _is_cached(string $pref_name, int $owner_uid, int $profile_id = null) { + $cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name); + return isset($this->cache[$cache_key]); + } + + private function _get_cache(string $pref_name, int $owner_uid, int $profile_id = null) { + $cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name); + + if (isset($this->cache[$cache_key])) + return $this->cache[$cache_key]; + + return null; + } + + private function _set_cache(string $pref_name, $value, int $owner_uid, int $profile_id = null) { + $cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name); + + $this->cache[$cache_key] = $value; + } + + static function set(string $pref_name, $value, int $owner_uid, int $profile_id = null, bool $strip_tags = true) { + return self::get_instance()->_set($pref_name, $value, $owner_uid, $profile_id); + } + + private function _delete(string $pref_name, int $owner_uid, int $profile_id = null) { + $sth = $this->pdo->prepare("DELETE FROM ttrss_user_prefs2 + WHERE pref_name = :name AND owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + + return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]); + } + + private function _set(string $pref_name, $value, int $owner_uid, int $profile_id = null, bool $strip_tags = true) { + if (!$profile_id) $profile_id = null; + + if ($profile_id && in_array($pref_name, self::_PROFILE_BLACKLIST)) + return false; + + if (isset(self::_DEFAULTS[$pref_name])) { + list ($def_val, $type_hint) = self::_DEFAULTS[$pref_name]; + + if ($strip_tags) + $value = trim(strip_tags($value)); + + $value = Config::cast_to($value, $type_hint); + + // is this a good idea or not? probably not (user-set value remains user-set even if its at default) + //if ($value == $def_val) + // return $this->_delete($pref_name, $owner_uid, $profile_id); + + if ($value == $this->_get($pref_name, $owner_uid, $profile_id)) + return false; + + $this->_set_cache($pref_name, $value, $owner_uid, $profile_id); + + $sth = $this->pdo->prepare("SELECT COUNT(pref_name) AS count FROM ttrss_user_prefs2 + WHERE pref_name = :name AND owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]); + + if ($row = $sth->fetch()) { + if ($row["count"] == 0) { + $sth = $this->pdo->prepare("INSERT INTO ttrss_user_prefs2 + (pref_name, value, owner_uid, profile) + VALUES + (:name, :value, :uid, :profile)"); + + return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name, "value" => $value ]); + + } else { + $sth = $this->pdo->prepare("UPDATE ttrss_user_prefs2 + SET value = :value + WHERE pref_name = :name AND owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + + return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name, "value" => $value ]); + } + } + } else { + user_error("Attempt to set invalid preference key: $pref_name (UID: $owner_uid, profile: $profile_id)", E_USER_WARNING); + } + + return false; + } + + function migrate(int $owner_uid, int $profile_id = null) { + if (get_schema_version() < 141) + return; + + if (!$profile_id) $profile_id = null; + + if (!$this->_get(Prefs::_PREFS_MIGRATED, $owner_uid, $profile_id)) { + + $in_nested_tr = false; + + try { + $this->pdo->beginTransaction(); + } catch (PDOException $e) { + $in_nested_tr = true; + } + + $sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs + WHERE owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + $sth->execute(["uid" => $owner_uid, "profile" => $profile_id]); + + while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { + if (isset(self::_DEFAULTS[$row["pref_name"]])) { + list ($def_val, $type_hint) = self::_DEFAULTS[$row["pref_name"]]; + + $user_val = Config::cast_to($row["value"], $type_hint); + + if ($user_val != $def_val) { + $this->_set($row["pref_name"], $user_val, $owner_uid, $profile_id); + } + } + } + + $this->_set(Prefs::_PREFS_MIGRATED, "1", $owner_uid, $profile_id); + + if (!$in_nested_tr) + $this->pdo->commit(); + + Logger::log(E_USER_NOTICE, sprintf("Migrated preferences of user %d (profile %d)", $owner_uid, $profile_id)); + } + } + + static function reset(int $owner_uid, int $profile_id = null) { + if (!$profile_id) $profile_id = null; + + $sth = Db::pdo()->prepare("DELETE FROM ttrss_user_prefs2 + WHERE owner_uid = :uid AND pref_name != :mig_key AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + + $sth->execute(["uid" => $owner_uid, "mig_key" => self::_PREFS_MIGRATED, "profile" => $profile_id]); + } +} diff --git a/classes/rpc.php b/classes/rpc.php index 8945823c6..630ea50cb 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -7,6 +7,36 @@ class RPC extends Handler_Protected { return array_search($method, $csrf_ignored) !== false; }*/ + private function _translations_as_array() { + + global $text_domains; + + $rv = []; + + foreach (array_keys($text_domains) as $domain) { + + /** @var gettext_reader $l10n */ + $l10n = _get_reader($domain); + + for ($i = 0; $i < $l10n->total; $i++) { + if (isset($l10n->table_originals[$i * 2 + 2]) && $orig = $l10n->get_original_string($i)) { + if(strpos($orig, "\000") !== false) { // Plural forms + $key = explode(chr(0), $orig); + + $rv[$key[0]] = _ngettext($key[0], $key[1], 1); // Singular + $rv[$key[1]] = _ngettext($key[0], $key[1], 2); // Plural + } else { + $translation = _dgettext($domain,$orig); + $rv[$orig] = $translation; + } + } + } + } + + return $rv; + } + + function togglepref() { $key = clean($_REQUEST["key"]); set_pref($key, !get_pref($key)); @@ -20,7 +50,7 @@ class RPC extends Handler_Protected { $key = clean($_REQUEST['key']); $value = $_REQUEST['value']; - set_pref($key, $value, false, $key != 'USER_STYLESHEET'); + set_pref($key, $value, $_SESSION["uid"], $key != 'USER_STYLESHEET'); print json_encode(array("param" =>$key, "value" => $value)); } @@ -66,7 +96,7 @@ class RPC extends Handler_Protected { function getRuntimeInfo() { $reply = [ - 'runtime-info' => $this->make_runtime_info() + 'runtime-info' => $this->_make_runtime_info() ]; print json_encode($reply); @@ -91,7 +121,6 @@ class RPC extends Handler_Protected { else $label_ids = array_map("intval", clean($_REQUEST["label_ids"] ?? [])); - // @phpstan-ignore-next-line $counters = is_array($feed_ids) ? Counters::get_conditional($feed_ids, $label_ids) : Counters::get_all(); $reply = [ @@ -139,21 +168,33 @@ class RPC extends Handler_Protected { $_SESSION["hasSandbox"] = clean($_REQUEST["hasSandbox"]) === "true"; $_SESSION["clientTzOffset"] = clean($_REQUEST["clientTzOffset"]); + $client_location = $_REQUEST["clientLocation"]; + $error = Errors::E_SUCCESS; + $error_params = []; + + $client_scheme = parse_url($client_location, PHP_URL_SCHEME); + $server_scheme = parse_url(get_self_url_prefix(), PHP_URL_SCHEME); - if (get_schema_version(true) != SCHEMA_VERSION) { + if (get_schema_version() != SCHEMA_VERSION) { $error = Errors::E_SCHEMA_MISMATCH; + } else if ($client_scheme != $server_scheme) { + $error = Errors::E_URL_SCHEME_MISMATCH; + $error_params["client_scheme"] = $client_scheme; + $error_params["server_scheme"] = $server_scheme; + $error_params["self_url_path"] = get_self_url_prefix(); } if ($error == Errors::E_SUCCESS) { $reply = []; - $reply['init-params'] = $this->make_init_params(); - $reply['runtime-info'] = $this->make_runtime_info(); + $reply['init-params'] = $this->_make_init_params(); + $reply['runtime-info'] = $this->_make_runtime_info(); + $reply['translations'] = $this->_translations_as_array(); print json_encode($reply); } else { - print Errors::to_json($error); + print Errors::to_json($error, $error_params); } } @@ -201,36 +242,42 @@ class RPC extends Handler_Protected { static function updaterandomfeed_real() { + $default_interval = (int) Prefs::get_default(Prefs::DEFAULT_UPDATE_INTERVAL); + // Test if the feed need a update (update interval exceded). if (Config::get(Config::DB_TYPE) == "pgsql") { $update_limit_qpart = "AND (( - ttrss_feeds.update_interval = 0 - AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL) + update_interval = 0 + AND p.value != '-1' + AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL) ) OR ( - ttrss_feeds.update_interval > 0 - AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL) + update_interval > 0 + AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL) ) OR ( - ttrss_feeds.update_interval >= 0 - AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) + update_interval >= 0 + AND p.value != '-1' + AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) ))"; } else { $update_limit_qpart = "AND (( - ttrss_feeds.update_interval = 0 - AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE) + update_interval = 0 + AND p.value != '-1' + AND last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(COALESCE(p.value, '$default_interval'), SIGNED INTEGER) MINUTE) ) OR ( - ttrss_feeds.update_interval > 0 - AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE) + update_interval > 0 + AND last_updated < DATE_SUB(NOW(), INTERVAL update_interval MINUTE) ) OR ( - ttrss_feeds.update_interval >= 0 - AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) + update_interval >= 0 + AND p.value != '-1' + AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) ))"; } // Test if feed is currently being updated by another process. if (Config::get(Config::DB_TYPE) == "pgsql") { - $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '5 minutes')"; + $updstart_thresh_qpart = "AND (last_update_started IS NULL OR last_update_started < NOW() - INTERVAL '5 minutes')"; } else { - $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))"; + $updstart_thresh_qpart = "AND (last_update_started IS NULL OR last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))"; } $random_qpart = Db::sql_random_function(); @@ -238,24 +285,24 @@ class RPC extends Handler_Protected { $pdo = Db::pdo(); // we could be invoked from public.php with no active session - if ($_SESSION["uid"]) { - $owner_check_qpart = "AND ttrss_feeds.owner_uid = ".$pdo->quote($_SESSION["uid"]); + if (!empty($_SESSION["uid"])) { + $owner_check_qpart = "AND f.owner_uid = ".$pdo->quote($_SESSION["uid"]); } else { $owner_check_qpart = ""; } - // We search for feed needing update. - $res = $pdo->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id + $query = "SELECT f.feed_url,f.id FROM - ttrss_feeds, ttrss_users, ttrss_user_prefs + ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON + (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') WHERE - ttrss_feeds.owner_uid = ttrss_users.id - AND ttrss_users.id = ttrss_user_prefs.owner_uid - AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' + f.owner_uid = u.id $owner_check_qpart $update_limit_qpart $updstart_thresh_qpart - ORDER BY $random_qpart LIMIT 30"); + ORDER BY $random_qpart LIMIT 30"; + + $res = $pdo->query($query); $num_updated = 0; @@ -338,7 +385,7 @@ class RPC extends Handler_Protected { $context = clean($_REQUEST['context']); if ($msg) { - Logger::get()->log_error(E_USER_WARNING, + Logger::log_error(E_USER_WARNING, $msg, 'client-js:' . $file, $line, $context); echo json_encode(array("message" => "HOST_ERROR_LOGGED")); @@ -372,13 +419,14 @@ class RPC extends Handler_Protected { print json_encode($rv); } - private function make_init_params() { + private function _make_init_params() { $params = array(); - foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS", - "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP", - "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", - "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) { + foreach ([Prefs::ON_CATCHUP_SHOW_NEXT_FEED, Prefs::HIDE_READ_FEEDS, + Prefs::ENABLE_FEED_CATS, Prefs::FEEDS_SORT_BY_UNREAD, + Prefs::CONFIRM_FEED_CATCHUP, Prefs::CDM_AUTO_CATCHUP, + Prefs::FRESH_ARTICLE_MAX_AGE, Prefs::HIDE_READ_SHOWS_SPECIAL, + Prefs::COMBINED_DISPLAY_MODE, Prefs::DEBUG_HEADLINE_IDS] as $param) { $params[strtolower($param)] = (int) get_pref($param); } @@ -387,14 +435,14 @@ class RPC extends Handler_Protected { $params["check_for_updates"] = Config::get(Config::CHECK_FOR_UPDATES); $params["icons_url"] = Config::get(Config::ICONS_URL); $params["cookie_lifetime"] = Config::get(Config::SESSION_COOKIE_LIFETIME); - $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE"); - $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); - $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); + $params["default_view_mode"] = get_pref(Prefs::_DEFAULT_VIEW_MODE); + $params["default_view_limit"] = (int) get_pref(Prefs::_DEFAULT_VIEW_LIMIT); + $params["default_view_order_by"] = get_pref(Prefs::_DEFAULT_VIEW_ORDER_BY); $params["bw_limit"] = (int) $_SESSION["bw_limit"]; $params["is_default_pw"] = Pref_Prefs::isdefaultpassword(); $params["label_base_index"] = LABEL_BASE_INDEX; - $theme = get_pref( "USER_CSS_THEME", false, false); + $theme = get_pref(Prefs::USER_CSS_THEME); $params["theme"] = theme_exists($theme) ? $theme : ""; $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names()); @@ -434,7 +482,7 @@ class RPC extends Handler_Protected { } } - static function make_runtime_info() { + static function _make_runtime_info() { $data = array(); $pdo = Db::pdo(); @@ -449,7 +497,7 @@ class RPC extends Handler_Protected { $data["max_feed_id"] = (int) $max_feed_id; $data["num_feeds"] = (int) $num_feeds; - $data['cdm_expanded'] = get_pref('CDM_EXPANDED'); + $data['cdm_expanded'] = get_pref(Prefs::CDM_EXPANDED); $data["labels"] = Labels::get_all($_SESSION["uid"]); if (Config::get(Config::LOG_DESTINATION) == 'sql' && $_SESSION['access_level'] >= 10) { diff --git a/classes/rssutils.php b/classes/rssutils.php index 6479d9f97..11a94162c 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -53,48 +53,54 @@ class RSSUtils { } static function update_daemon_common($limit = null, $options = []) { - $schema_version = get_schema_version(); - if (!$limit) $limit = Config::get(Config::DAEMON_FEED_LIMIT); - if ($schema_version != SCHEMA_VERSION) { + if (get_schema_version() != SCHEMA_VERSION) { die("Schema version is wrong, please upgrade the database.\n"); } $pdo = Db::pdo(); if (!Config::get(Config::SINGLE_USER_MODE) && Config::get(Config::DAEMON_UPDATE_LOGIN_LIMIT) > 0) { + $login_limit = (int) Config::get(Config::DAEMON_UPDATE_LOGIN_LIMIT); + if (Config::get(Config::DB_TYPE) == "pgsql") { - $login_thresh_qpart = "AND ttrss_users.last_login >= NOW() - INTERVAL '".Config::get(Config::DAEMON_UPDATE_LOGIN_LIMIT)." days'"; + $login_thresh_qpart = "AND last_login >= NOW() - INTERVAL '$login_limit days'"; } else { - $login_thresh_qpart = "AND ttrss_users.last_login >= DATE_SUB(NOW(), INTERVAL ".Config::get(Config::DAEMON_UPDATE_LOGIN_LIMIT)." DAY)"; + $login_thresh_qpart = "AND last_login >= DATE_SUB(NOW(), INTERVAL $login_limit DAY)"; } } else { $login_thresh_qpart = ""; } + $default_interval = (int) Prefs::get_default(Prefs::DEFAULT_UPDATE_INTERVAL); + if (Config::get(Config::DB_TYPE) == "pgsql") { $update_limit_qpart = "AND (( - ttrss_feeds.update_interval = 0 - AND ttrss_user_prefs.value != '-1' - AND last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL) + update_interval = 0 + AND p.value != '-1' + AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL) + ) OR ( + update_interval > 0 + AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL) ) OR ( - ttrss_feeds.update_interval > 0 - AND last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL) - ) OR ((last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) - AND ttrss_feeds.update_interval >= 0 - AND ttrss_user_prefs.value != '-1'))"; + update_interval >= 0 + AND p.value != '-1' + AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) + ))"; } else { $update_limit_qpart = "AND (( - ttrss_feeds.update_interval = 0 - AND ttrss_user_prefs.value != '-1' - AND last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE) + update_interval = 0 + AND p.value != '-1' + AND last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(COALESCE(p.value, '$default_interval'), SIGNED INTEGER) MINUTE) ) OR ( - ttrss_feeds.update_interval > 0 - AND last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE) - ) OR ((last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) - AND ttrss_feeds.update_interval >= 0 - AND ttrss_user_prefs.value != '-1'))"; + update_interval > 0 + AND last_updated < DATE_SUB(NOW(), INTERVAL update_interval MINUTE) + ) OR ( + update_interval >= 0 + AND p.value != '-1' + AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL) + ))"; } // Test if feed is currently being updated by another process. @@ -108,20 +114,23 @@ class RSSUtils { // Update the least recently updated feeds first $query_order = "ORDER BY last_updated"; - if (Config::get(Config::DB_TYPE) == "pgsql") $query_order .= " NULLS FIRST"; - $query = "SELECT DISTINCT ttrss_feeds.feed_url, ttrss_feeds.last_updated + if (Config::get(Config::DB_TYPE) == "pgsql") + $query_order .= " NULLS FIRST"; + + $query = "SELECT f.feed_url, f.last_updated FROM - ttrss_feeds, ttrss_users, ttrss_user_prefs + ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON + (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') WHERE - ttrss_feeds.owner_uid = ttrss_users.id - AND ttrss_user_prefs.profile IS NULL - AND ttrss_users.id = ttrss_user_prefs.owner_uid - AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' - $login_thresh_qpart $update_limit_qpart + f.owner_uid = u.id + $login_thresh_qpart + $update_limit_qpart $updstart_thresh_qpart $query_order $query_limit"; + //print "$query\n"; + $res = $pdo->query($query); $feeds_to_update = array(); @@ -144,34 +153,38 @@ class RSSUtils { $nf = 0; $bstarted = microtime(true); - $batch_owners = array(); + $batch_owners = []; - // since we have the data cached, we can deal with other feeds with the same url - $usth = $pdo->prepare("SELECT - DISTINCT ttrss_feeds.id, + $user_query = "SELECT f.id, last_updated, - ttrss_feeds.owner_uid, - ttrss_feeds.title - FROM ttrss_feeds, ttrss_users, ttrss_user_prefs WHERE - ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND - ttrss_users.id = ttrss_user_prefs.owner_uid AND - ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND - ttrss_user_prefs.profile IS NULL AND - feed_url = ? - $update_limit_qpart + f.owner_uid, + u.login AS owner, + f.title + FROM ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON + (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') + WHERE + f.owner_uid = u.id + AND feed_url = :feed $login_thresh_qpart - ORDER BY ttrss_feeds.id $query_limit"); + $update_limit_qpart + ORDER BY f.id $query_limit"; + + //print "$user_query\n"; + + // since we have feed xml cached, we can deal with other feeds with the same url + $usth = $pdo->prepare($user_query); foreach ($feeds_to_update as $feed) { Debug::log("Base feed: $feed"); - $usth->execute([$feed]); + $usth->execute(["feed" => $feed]); if ($tline = $usth->fetch()) { - Debug::log(sprintf("=> %s (ID: %d, UID: %d), last updated: %s", $tline["title"], $tline["id"], $tline["owner_uid"], + Debug::log(sprintf("=> %s (ID: %d, U: %s [%d]), last updated: %s", $tline["title"], $tline["id"], + $tline["owner"], $tline["owner_uid"], $tline["last_updated"] ? $tline["last_updated"] : "never")); - if (array_search($tline["owner_uid"], $batch_owners) === false) + if (!in_array($tline["owner_uid"], $batch_owners)) array_push($batch_owners, $tline["owner_uid"]); $fstarted = microtime(true); @@ -201,7 +214,7 @@ class RSSUtils { Debug::log("!! Last error: $error_message"); - Logger::get()->log(E_USER_NOTICE, + Logger::log(E_USER_NOTICE, sprintf("Update process for feed %d (%s, owner UID: %d) failed with exit code: %d (%s).", $tline["id"], clean($tline["title"]), $tline["owner_uid"], $exit_code, clean($error_message))); @@ -218,7 +231,7 @@ class RSSUtils { if (!self::update_rss_feed($tline["id"], true)) { global $fetch_last_error; - Logger::get()->log(E_USER_NOTICE, + Logger::log(E_USER_NOTICE, sprintf("Update request for feed %d (%s, owner UID: %d) failed: %s.", $tline["id"], clean($tline["title"]), $tline["owner_uid"], clean($fetch_last_error))); } @@ -226,7 +239,7 @@ class RSSUtils { Debug::log(sprintf("<= %.4f (sec) (not using a separate process)", microtime(true) - $fstarted)); } catch (PDOException $e) { - Logger::get()->log_error(E_USER_WARNING, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()); + Logger::log_error(E_USER_WARNING, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()); try { $pdo->rollback(); @@ -275,7 +288,7 @@ class RSSUtils { $fetch_url = $row["feed_url"]; $pluginhost = new PluginHost(); - $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); + $user_plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid); $pluginhost->load(Config::get(Config::PLUGINS), PluginHost::KIND_ALL); $pluginhost->load((string)$user_plugins, PluginHost::KIND_USER, $owner_uid); @@ -386,7 +399,7 @@ class RSSUtils { $feed_language = mb_strtolower($row["feed_language"]); if (!$feed_language) - $feed_language = mb_strtolower(get_pref('DEFAULT_SEARCH_LANGUAGE', $owner_uid)); + $feed_language = mb_strtolower(get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE, $owner_uid)); if (!$feed_language) $feed_language = 'simple'; @@ -400,7 +413,7 @@ class RSSUtils { $cache_filename = Config::get(Config::CACHE_DIR) . "/feeds/" . sha1($fetch_url) . ".xml"; $pluginhost = new PluginHost(); - $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); + $user_plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid); $pluginhost->load(Config::get(Config::PLUGINS), PluginHost::KIND_ALL); $pluginhost->load((string)$user_plugins, PluginHost::KIND_USER, $owner_uid); @@ -1193,7 +1206,7 @@ class RSSUtils { $boring_tags = array_map('trim', explode(",", mb_strtolower( - get_pref('BLACKLISTED_TAGS', $owner_uid)))); + get_pref(Prefs::BLACKLISTED_TAGS, $owner_uid)))); $entry_tags = FeedItem_Common::normalize_categories( array_unique( @@ -1604,7 +1617,7 @@ class RSSUtils { $sth->execute(); while ($row = $sth->fetch()) { - Logger::get()->log(E_USER_NOTICE, + Logger::log(E_USER_NOTICE, sprintf("Auto disabling feed %d (%s, UID: %d) because it failed to update for %d days.", $row["id"], clean($row["title"]), $row["owner_uid"], Config::get(Config::DAEMON_UNSUCCESSFUL_DAYS_LIMIT))); diff --git a/classes/sanitizer.php b/classes/sanitizer.php index 2682471d0..52feb5e28 100644 --- a/classes/sanitizer.php +++ b/classes/sanitizer.php @@ -95,7 +95,7 @@ class Sanitizer { } if ($entry->hasAttribute('src') && - ($owner && get_pref("STRIP_IMAGES", $owner)) || $force_remove_images || ($_SESSION["bw_limit"] ?? false)) { + ($owner && get_pref(Prefs::STRIP_IMAGES, $owner)) || $force_remove_images || ($_SESSION["bw_limit"] ?? false)) { $p = $doc->createElement('p'); diff --git a/classes/timehelper.php b/classes/timehelper.php index 7dff71669..4317f343f 100644 --- a/classes/timehelper.php +++ b/classes/timehelper.php @@ -7,16 +7,16 @@ class TimeHelper { if ($eta_min && time() + $tz_offset - $timestamp < 3600) { return T_sprintf("%d min", date("i", time() + $tz_offset - $timestamp)); } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() + $tz_offset)) { - $format = get_pref('SHORT_DATE_FORMAT', $owner_uid); + $format = get_pref(Prefs::SHORT_DATE_FORMAT, $owner_uid); if (strpos((strtolower($format)), "a") === false) return date("G:i", $timestamp); else return date("g:i a", $timestamp); } else if (date("Y", $timestamp) == date("Y", time() + $tz_offset)) { - $format = get_pref('SHORT_DATE_FORMAT', $owner_uid); + $format = get_pref(Prefs::SHORT_DATE_FORMAT, $owner_uid); return date($format, $timestamp); } else { - $format = get_pref('LONG_DATE_FORMAT', $owner_uid); + $format = get_pref(Prefs::LONG_DATE_FORMAT, $owner_uid); return date($format, $timestamp); } } @@ -37,7 +37,7 @@ class TimeHelper { # We store date in UTC internally $dt = new DateTime($timestamp, $utc_tz); - $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid); + $user_tz_string = get_pref(Prefs::USER_TIMEZONE, $owner_uid); if ($user_tz_string != 'Automatic') { @@ -59,9 +59,9 @@ class TimeHelper { $tz_offset, $owner_uid, $eta_min); } else { if ($long) - $format = get_pref('LONG_DATE_FORMAT', $owner_uid); + $format = get_pref(Prefs::LONG_DATE_FORMAT, $owner_uid); else - $format = get_pref('SHORT_DATE_FORMAT', $owner_uid); + $format = get_pref(Prefs::SHORT_DATE_FORMAT, $owner_uid); return date($format, $user_timestamp); } diff --git a/classes/userhelper.php b/classes/userhelper.php index 82a2fe05f..ca673cf58 100644 --- a/classes/userhelper.php +++ b/classes/userhelper.php @@ -18,7 +18,9 @@ class UserHelper { if ($user_id && !$check_only) { - session_start(); + if (session_status() != PHP_SESSION_ACTIVE) + session_start(); + session_regenerate_id(true); $_SESSION["uid"] = $user_id; @@ -41,11 +43,12 @@ class UserHelper { $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']); $_SESSION["pwd_hash"] = $row["pwd_hash"]; - Pref_Prefs::_init_user_prefs($_SESSION["uid"]); - return true; } + if ($login && $password && !$user_id && !$check_only) + Logger::log(E_USER_WARNING, "Failed login attempt for $login (service: $service) from " . UserHelper::get_user_ip()); + return false; } else { @@ -64,8 +67,6 @@ class UserHelper { $_SESSION["ip_address"] = UserHelper::get_user_ip(); - Pref_Prefs::_init_user_prefs($_SESSION["uid"]); - return true; } } @@ -75,7 +76,7 @@ class UserHelper { if (!$pluginhost) $pluginhost = PluginHost::getInstance(); if ($owner_uid && SCHEMA_VERSION >= 100 && empty($_SESSION["safe_mode"])) { - $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); + $plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid); $pluginhost->load((string)$plugins, PluginHost::KIND_USER, $owner_uid); @@ -89,17 +90,20 @@ class UserHelper { $pdo = Db::pdo(); if (Config::get(Config::SINGLE_USER_MODE)) { - @session_start(); + if (session_status() != PHP_SESSION_ACTIVE) + session_start(); + self::authenticate("admin", null); startup_gettext(); self::load_user_plugins($_SESSION["uid"]); } else { - if (!\Sessions\validate_session()) $_SESSION["uid"] = false; + if (!\Sessions\validate_session()) + $_SESSION["uid"] = null; if (empty($_SESSION["uid"])) { if (Config::get(Config::AUTH_AUTO_LOGIN) && self::authenticate(null, null)) { - $_SESSION["ref_schema_version"] = get_schema_version(true); + $_SESSION["ref_schema_version"] = get_schema_version(); } else { self::authenticate(null, null, true); } @@ -127,7 +131,7 @@ class UserHelper { } static function print_user_stylesheet() { - $value = get_pref('USER_STYLESHEET'); + $value = get_pref(Prefs::USER_STYLESHEET); if ($value) { print "<style type='text/css' id='user_css_style'>"; |