summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/api.php2
-rw-r--r--classes/config.php132
-rwxr-xr-xclasses/db.php10
-rwxr-xr-xclasses/handler/public.php4
-rwxr-xr-xclasses/logger.php2
-rw-r--r--classes/mailer.php2
-rw-r--r--classes/pref/prefs.php16
-rw-r--r--classes/pref/system.php4
-rw-r--r--classes/pref/users.php2
-rw-r--r--classes/prefs.php2
-rwxr-xr-xclasses/rpc.php7
-rwxr-xr-xclasses/rssutils.php6
-rw-r--r--classes/urlhelper.php11
13 files changed, 173 insertions, 27 deletions
diff --git a/classes/api.php b/classes/api.php
index a1ed7968c..407afabad 100755
--- a/classes/api.php
+++ b/classes/api.php
@@ -792,7 +792,7 @@ class API extends Handler {
list ($flavor_image, $flavor_stream, $flavor_kind) = Article::_get_image($enclosures,
$line["content"], // unsanitized
- $line["site_url"],
+ $line["site_url"] ?? "", // could be null if archived article
$headline_row);
$headline_row["flavor_image"] = $flavor_image;
diff --git a/classes/config.php b/classes/config.php
index 567a019c6..6e8d4533f 100644
--- a/classes/config.php
+++ b/classes/config.php
@@ -6,9 +6,22 @@ class Config {
const T_STRING = 2;
const T_INT = 3;
- const SCHEMA_VERSION = 144;
+ const SCHEMA_VERSION = 145;
- // override defaults, defined below in _DEFAULTS[], via environment: DB_TYPE becomes TTRSS_DB_TYPE, etc
+ /* override defaults, defined below in _DEFAULTS[], prefixing with _ENVVAR_PREFIX:
+
+ DB_TYPE becomes:
+
+ .env:
+
+ TTRSS_DB_TYPE=pgsql
+
+ or config.php:
+
+ putenv('TTRSS_DB_TYPE=pgsql');
+
+ etc, etc.
+ */
const DB_TYPE = "DB_TYPE";
const DB_HOST = "DB_HOST";
@@ -16,48 +29,148 @@ class Config {
const DB_NAME = "DB_NAME";
const DB_PASS = "DB_PASS";
const DB_PORT = "DB_PORT";
+ // database credentials
+
const MYSQL_CHARSET = "MYSQL_CHARSET";
+ // connection charset for MySQL. if you have a legacy database and/or experience
+ // garbage unicode characters with this option, try setting it to a blank string.
+
const SELF_URL_PATH = "SELF_URL_PATH";
+ // this should be set to a fully qualified URL used to access
+ // your tt-rss instance over the net, such as: https://example.com/tt-rss/
+ // if your tt-rss instance is behind a reverse proxy, use external URL.
+ // tt-rss will likely help you pick correct value for this on startup
+
const SINGLE_USER_MODE = "SINGLE_USER_MODE";
+ // operate in single user mode, disables all functionality related to
+ // multiple users and authentication. enabling this assumes you have
+ // your tt-rss directory protected by other means (e.g. http auth).
+
const SIMPLE_UPDATE_MODE = "SIMPLE_UPDATE_MODE";
+ // enables fallback update mode where tt-rss tries to update feeds in
+ // background while tt-rss is open in your browser.
+ // if you don't have a lot of feeds and don't want to or can't run
+ // background processes while not running tt-rss, this method is generally
+ // viable to keep your feeds up to date.
+
const PHP_EXECUTABLE = "PHP_EXECUTABLE";
+ // use this PHP CLI executable to start various tasks
+
const LOCK_DIRECTORY = "LOCK_DIRECTORY";
+ // base directory for lockfiles (must be writable)
+
const CACHE_DIR = "CACHE_DIR";
+ // base directory for local cache (must be writable)
+
const ICONS_DIR = "ICONS_DIR";
const ICONS_URL = "ICONS_URL";
+ // directory and URL for feed favicons (directory must be writable)
+
const AUTH_AUTO_CREATE = "AUTH_AUTO_CREATE";
+ // auto create users authenticated via external modules
+
const AUTH_AUTO_LOGIN = "AUTH_AUTO_LOGIN";
+ // auto log in users authenticated via external modules i.e. auth_remote
+
const FORCE_ARTICLE_PURGE = "FORCE_ARTICLE_PURGE";
+ // unconditinally purge all articles older than this amount, in days
+ // overrides user-controlled purge interval
+
const SESSION_COOKIE_LIFETIME = "SESSION_COOKIE_LIFETIME";
+ // default lifetime of a session (e.g. login) cookie. In seconds,
+ // 0 means cookie will be deleted when browser closes.
+
const SMTP_FROM_NAME = "SMTP_FROM_NAME";
const SMTP_FROM_ADDRESS = "SMTP_FROM_ADDRESS";
+ // send email using this name and address
+
const DIGEST_SUBJECT = "DIGEST_SUBJECT";
+ // default subject for email digest
+
const CHECK_FOR_UPDATES = "CHECK_FOR_UPDATES";
+ // enable built-in update checker, both for core code and plugins (using git)
+
const PLUGINS = "PLUGINS";
+ // system plugins enabled for all users, comma separated list, no quotes
+ // keep at least one auth module in there (i.e. auth_internal)
+
const LOG_DESTINATION = "LOG_DESTINATION";
+ // available options: sql (default, event log), syslog, stdout (for debugging)
+
const LOCAL_OVERRIDE_STYLESHEET = "LOCAL_OVERRIDE_STYLESHEET";
+ // link this stylesheet on all pages (if it exists), should be placed in themes.local
+
+ const LOCAL_OVERRIDE_JS = "LOCAL_OVERRIDE_JS";
+ // same but this javascript file (you can use that for polyfills), should be placed in themes.local
+
const DAEMON_MAX_CHILD_RUNTIME = "DAEMON_MAX_CHILD_RUNTIME";
+ // in seconds, terminate update tasks that ran longer than this interval
+
const DAEMON_MAX_JOBS = "DAEMON_MAX_JOBS";
+ // max concurrent update jobs forking update daemon starts
+
const FEED_FETCH_TIMEOUT = "FEED_FETCH_TIMEOUT";
+ // How long to wait for response when requesting feed from a site (seconds)
+
const FEED_FETCH_NO_CACHE_TIMEOUT = "FEED_FETCH_NO_CACHE_TIMEOUT";
+ // Same but not cached
+
const FILE_FETCH_TIMEOUT = "FILE_FETCH_TIMEOUT";
+ // Default timeout when fetching files from remote sites
+
const FILE_FETCH_CONNECT_TIMEOUT = "FILE_FETCH_CONNECT_TIMEOUT";
+ // How long to wait for initial response from website when fetching files from remote sites
+
const DAEMON_UPDATE_LOGIN_LIMIT = "DAEMON_UPDATE_LOGIN_LIMIT";
+ // stop updating feeds if user haven't logged in for X days
+
const DAEMON_FEED_LIMIT = "DAEMON_FEED_LIMIT";
+ // how many feeds to update in one batch
+
const DAEMON_SLEEP_INTERVAL = "DAEMON_SLEEP_INTERVAL";
+ // default sleep interval between feed updates (sec)
+
const MAX_CACHE_FILE_SIZE = "MAX_CACHE_FILE_SIZE";
+ // do not cache files larger than that (bytes)
+
const MAX_DOWNLOAD_FILE_SIZE = "MAX_DOWNLOAD_FILE_SIZE";
+ // do not download files larger than that (bytes)
+
const MAX_FAVICON_FILE_SIZE = "MAX_FAVICON_FILE_SIZE";
+ // max file size for downloaded favicons (bytes)
+
const CACHE_MAX_DAYS = "CACHE_MAX_DAYS";
+ // max age in days for various automatically cached (temporary) files
+
const MAX_CONDITIONAL_INTERVAL = "MAX_CONDITIONAL_INTERVAL";
+ // max interval between forced unconditional updates for servers not complying with http if-modified-since (seconds)
+
const DAEMON_UNSUCCESSFUL_DAYS_LIMIT = "DAEMON_UNSUCCESSFUL_DAYS_LIMIT";
+ // automatically disable updates for feeds which failed to
+ // update for this amount of days; 0 disables
+
const LOG_SENT_MAIL = "LOG_SENT_MAIL";
+ // log all sent emails in the event log
+
const HTTP_PROXY = "HTTP_PROXY";
+ // use HTTP proxy for requests
+
const FORBID_PASSWORD_CHANGES = "FORBID_PASSWORD_CHANGES";
+ // prevent users from changing passwords
+
const SESSION_NAME = "SESSION_NAME";
+ // default session cookie name
+
const CHECK_FOR_PLUGIN_UPDATES = "CHECK_FOR_PLUGIN_UPDATES";
+ // enable plugin update checker (using git)
+
const ENABLE_PLUGIN_INSTALLER = "ENABLE_PLUGIN_INSTALLER";
+ // allow installing first party plugins using plugin installer in prefs
+
+ const AUTH_MIN_INTERVAL = "AUTH_MIN_INTERVAL";
+ // minimum amount of seconds required between authentication attempts
+ // default values for all of the above:
private const _DEFAULTS = [
Config::DB_TYPE => [ "pgsql", Config::T_STRING ],
Config::DB_HOST => [ "db", Config::T_STRING ],
@@ -87,6 +200,8 @@ class Config {
Config::LOG_DESTINATION => [ Logger::LOG_DEST_SQL, Config::T_STRING ],
Config::LOCAL_OVERRIDE_STYLESHEET => [ "local-overrides.css",
Config::T_STRING ],
+ Config::LOCAL_OVERRIDE_JS => [ "local-overrides.js",
+ Config::T_STRING ],
Config::DAEMON_MAX_CHILD_RUNTIME => [ 1800, Config::T_INT ],
Config::DAEMON_MAX_JOBS => [ 2, Config::T_INT ],
Config::FEED_FETCH_TIMEOUT => [ 45, Config::T_INT ],
@@ -108,6 +223,7 @@ class Config {
Config::SESSION_NAME => [ "ttrss_sid", Config::T_STRING ],
Config::CHECK_FOR_PLUGIN_UPDATES => [ "true", Config::T_BOOL ],
Config::ENABLE_PLUGIN_INSTALLER => [ "true", Config::T_BOOL ],
+ Config::AUTH_MIN_INTERVAL => [ 5, Config::T_INT ],
];
private static $instance;
@@ -503,4 +619,16 @@ class Config {
private static function format_error($msg) {
return "<div class=\"alert alert-danger\">$msg</div>";
}
+
+ static function get_override_links() {
+ $rv = "";
+
+ $local_css = get_theme_path(self::get(self::LOCAL_OVERRIDE_STYLESHEET));
+ if ($local_css) $rv .= stylesheet_tag($local_css);
+
+ $local_js = get_theme_path(self::get(self::LOCAL_OVERRIDE_JS));
+ if ($local_js) $rv .= javascript_tag($local_js);
+
+ return $rv;
+ }
}
diff --git a/classes/db.php b/classes/db.php
index 008275bca..a09c44628 100755
--- a/classes/db.php
+++ b/classes/db.php
@@ -14,6 +14,9 @@ class Db
ORM::configure('username', Config::get(Config::DB_USER));
ORM::configure('password', Config::get(Config::DB_PASS));
ORM::configure('return_result_sets', true);
+ if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET)) {
+ ORM::configure('driver_options', array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . Config::get(Config::MYSQL_CHARSET)));
+ }
}
static function NOW() {
@@ -27,8 +30,13 @@ class Db
public static function get_dsn() {
$db_port = Config::get(Config::DB_PORT) ? ';port=' . Config::get(Config::DB_PORT) : '';
$db_host = Config::get(Config::DB_HOST) ? ';host=' . Config::get(Config::DB_HOST) : '';
+ if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET)) {
+ $db_charset = ';charset=' . Config::get(Config::MYSQL_CHARSET);
+ } else {
+ $db_charset = '';
+ }
- return Config::get(Config::DB_TYPE) . ':dbname=' . Config::get(Config::DB_NAME) . $db_host . $db_port;
+ return Config::get(Config::DB_TYPE) . ':dbname=' . Config::get(Config::DB_NAME) . $db_host . $db_port . $db_charset;
}
// this really shouldn't be used unless a separate PDO connection is needed
diff --git a/classes/handler/public.php b/classes/handler/public.php
index 2de073cc2..d5319c306 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -638,9 +638,7 @@ class Handler_Public extends Handler {
} ?>
- <?php if (theme_exists(Config::get(Config::LOCAL_OVERRIDE_STYLESHEET))) {
- echo stylesheet_tag(get_theme_path(Config::get(Config::LOCAL_OVERRIDE_STYLESHEET)));
- } ?>
+ <?= Config::get_override_links() ?>
<style type="text/css">
@media (prefers-color-scheme: dark) {
diff --git a/classes/logger.php b/classes/logger.php
index f8abb5f84..42ab4452c 100755
--- a/classes/logger.php
+++ b/classes/logger.php
@@ -46,7 +46,7 @@ class Logger {
if ($this->adapter)
return $this->adapter->log_error($errno, $errstr, '', 0, $context);
else
- return false;
+ return user_error($errstr, $errno);
}
private function __clone() {
diff --git a/classes/mailer.php b/classes/mailer.php
index 564338f69..8238904ee 100644
--- a/classes/mailer.php
+++ b/classes/mailer.php
@@ -4,7 +4,7 @@ class Mailer {
function mail($params) {
- $to_name = $params["to_name"];
+ $to_name = $params["to_name"] ?? "";
$to_address = $params["to_address"];
$subject = $params["subject"];
$message = $params["message"];
diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php
index 16c41df9d..1d61059cb 100644
--- a/classes/pref/prefs.php
+++ b/classes/pref/prefs.php
@@ -54,6 +54,7 @@ class Pref_Prefs extends Handler_Protected {
'BLOCK_SEPARATOR',
Prefs::COMBINED_DISPLAY_MODE,
Prefs::CDM_EXPANDED,
+ Prefs::CDM_ENABLE_GRID,
'BLOCK_SEPARATOR',
Prefs::CDM_AUTO_CATCHUP,
Prefs::VFEED_GROUP_BY_FEED,
@@ -117,6 +118,7 @@ class Pref_Prefs extends Handler_Protected {
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")),
Prefs::DISABLE_CONDITIONAL_COUNTERS => array(__("Disable conditional counter updates"), __("May increase server load")),
+ Prefs::CDM_ENABLE_GRID => array(__("Grid view"), __("On wider screens, if always expanded")),
];
// hidden in the main prefs UI (use to hide things that have description set above)
@@ -1434,10 +1436,10 @@ class Pref_Prefs extends Handler_Protected {
<div class='panel panel-scrollable'>
<table width='100%' id='app-password-list'>
<tr>
- <th width='2%'> </th>
- <th align='left'><?= __("Description") ?></th>
- <th align='right'><?= __("Created") ?></th>
- <th align='right'><?= __("Last used") ?></th>
+ <th class="checkbox"> </th>
+ <th width='50%'><?= __("Description") ?></th>
+ <th><?= __("Created") ?></th>
+ <th><?= __("Last used") ?></th>
</tr>
<?php
@@ -1448,16 +1450,16 @@ class Pref_Prefs extends Handler_Protected {
foreach ($passwords as $pass) { ?>
<tr data-row-id='<?= $pass['id'] ?>'>
- <td align='center'>
+ <td class="checkbox">
<input onclick='Tables.onRowChecked(this)' dojoType='dijit.form.CheckBox' type='checkbox'>
</td>
<td>
<?= htmlspecialchars($pass["title"]) ?>
</td>
- <td align='right' class='text-muted'>
+ <td class='text-muted'>
<?= TimeHelper::make_local_datetime($pass['created'], false) ?>
</td>
- <td align='right' class='text-muted'>
+ <td class='text-muted'>
<?= TimeHelper::make_local_datetime($pass['last_used'], false) ?>
</td>
</tr>
diff --git a/classes/pref/system.php b/classes/pref/system.php
index c79b5095d..8bebcc7ce 100644
--- a/classes/pref/system.php
+++ b/classes/pref/system.php
@@ -42,10 +42,10 @@ class Pref_System extends Handler_Administrative {
switch ($severity) {
case E_USER_ERROR:
- $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE ];
+ $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR ];
break;
case E_USER_WARNING:
- $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED ];
+ $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED ];
break;
}
diff --git a/classes/pref/users.php b/classes/pref/users.php
index 2e3dc4b67..76a879efd 100644
--- a/classes/pref/users.php
+++ b/classes/pref/users.php
@@ -117,7 +117,7 @@ class Pref_Users extends Handler_Administrative {
$user->login = mb_strtolower($login);
$user->access_level = (int) clean($_REQUEST["access_level"]);
$user->email = clean($_REQUEST["email"]);
- $user->otp_enabled = checkbox_to_sql_bool($_REQUEST["otp_enabled"]);
+ $user->otp_enabled = checkbox_to_sql_bool($_REQUEST["otp_enabled"] ?? "");
// force new OTP secret when next enabled
if (Config::get_schema_version() >= 143 && !$user->otp_enabled) {
diff --git a/classes/prefs.php b/classes/prefs.php
index 24f0f7a80..85e7c34db 100644
--- a/classes/prefs.php
+++ b/classes/prefs.php
@@ -60,6 +60,7 @@ class Prefs {
const DEBUG_HEADLINE_IDS = "DEBUG_HEADLINE_IDS";
const DISABLE_CONDITIONAL_COUNTERS = "DISABLE_CONDITIONAL_COUNTERS";
const WIDESCREEN_MODE = "WIDESCREEN_MODE";
+ const CDM_ENABLE_GRID = "CDM_ENABLE_GRID";
private const _DEFAULTS = [
Prefs::PURGE_OLD_DAYS => [ 60, Config::T_INT ],
@@ -120,6 +121,7 @@ class Prefs {
Prefs::DEBUG_HEADLINE_IDS => [ false, Config::T_BOOL ],
Prefs::DISABLE_CONDITIONAL_COUNTERS => [ false, Config::T_BOOL ],
Prefs::WIDESCREEN_MODE => [ false, Config::T_BOOL ],
+ Prefs::CDM_ENABLE_GRID => [ false, Config::T_BOOL ],
];
const _PROFILE_BLACKLIST = [
diff --git a/classes/rpc.php b/classes/rpc.php
index 35125ae04..bde0b1e46 100755
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -431,7 +431,7 @@ class RPC extends Handler_Protected {
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) {
+ Prefs::COMBINED_DISPLAY_MODE, Prefs::DEBUG_HEADLINE_IDS, Prefs::CDM_ENABLE_GRID] as $param) {
$params[strtolower($param)] = (int) get_pref($param);
}
@@ -603,6 +603,7 @@ class RPC extends Handler_Protected {
"feed_catchup" => __("Mark as read"),
"feed_reverse" => __("Reverse headlines"),
"feed_toggle_vgroup" => __("Toggle headline grouping"),
+ "feed_toggle_grid" => __("Toggle grid view"),
"feed_debug_update" => __("Debug feed update"),
"feed_debug_viewfeed" => __("Debug viewfeed()"),
"catchup_all" => __("Mark all feeds as read"),
@@ -663,6 +664,7 @@ class RPC extends Handler_Protected {
"a e" => "toggle_full_text",
"e" => "email_article",
"a q" => "close_article",
+ "a s" => "article_span_grid",
"a a" => "select_all",
"a u" => "select_unread",
"a U" => "select_marked",
@@ -676,8 +678,9 @@ class RPC extends Handler_Protected {
"f q" => "feed_catchup",
"f x" => "feed_reverse",
"f g" => "feed_toggle_vgroup",
+ "f G" => "feed_toggle_grid",
"f D" => "feed_debug_update",
- "f G" => "feed_debug_viewfeed",
+ "f %" => "feed_debug_viewfeed",
"f C" => "toggle_combined_mode",
"f c" => "toggle_cdm_expanded",
"Q" => "catchup_all",
diff --git a/classes/rssutils.php b/classes/rssutils.php
index e6bf08ab1..d797838ba 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -1422,8 +1422,8 @@ class RSSUtils {
$matches = array();
foreach ($filters as $filter) {
- $match_any_rule = $filter["match_any_rule"];
- $inverse = $filter["inverse"];
+ $match_any_rule = $filter["match_any_rule"] ?? false;
+ $inverse = $filter["inverse"] ?? false;
$filter_match = false;
$last_processed_rule = false;
@@ -1431,7 +1431,7 @@ class RSSUtils {
$match = false;
$reg_exp = str_replace('/', '\/', (string)$rule["reg_exp"]);
$reg_exp = str_replace("\n", "", $reg_exp); // reg_exp may be formatted with CRs now because of textarea, we need to strip those
- $rule_inverse = $rule["inverse"];
+ $rule_inverse = $rule["inverse"] ?? false;
$last_processed_rule = $rule;
if (empty($reg_exp))
diff --git a/classes/urlhelper.php b/classes/urlhelper.php
index 55d5d1e6a..46d80a0e6 100644
--- a/classes/urlhelper.php
+++ b/classes/urlhelper.php
@@ -271,10 +271,15 @@ class UrlHelper {
// holy shit closures in php
// download & upload are *expected* sizes respectively, could be zero
- curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use( &$max_size) {
- Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
+ curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use(&$max_size, $url) {
+ //Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
- return ($downloaded > $max_size) ? 1 : 0; // if max size is set, abort when exceeding it
+ if ($downloaded > $max_size) {
+ Debug::log("curl: reached max size of $max_size bytes requesting $url, aborting.", Debug::LOG_VERBOSE);
+ return 1;
+ }
+
+ return 0;
});
}