diff options
Diffstat (limited to 'classes')
-rwxr-xr-x | classes/api.php | 2 | ||||
-rw-r--r-- | classes/config.php | 132 | ||||
-rwxr-xr-x | classes/db.php | 10 | ||||
-rwxr-xr-x | classes/handler/public.php | 4 | ||||
-rwxr-xr-x | classes/logger.php | 2 | ||||
-rw-r--r-- | classes/mailer.php | 2 | ||||
-rw-r--r-- | classes/pref/prefs.php | 16 | ||||
-rw-r--r-- | classes/pref/system.php | 4 | ||||
-rw-r--r-- | classes/pref/users.php | 2 | ||||
-rw-r--r-- | classes/prefs.php | 2 | ||||
-rwxr-xr-x | classes/rpc.php | 7 | ||||
-rwxr-xr-x | classes/rssutils.php | 6 | ||||
-rw-r--r-- | classes/urlhelper.php | 11 |
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; }); } |