2021-02-22 18:49:09 +00:00
< ? php
class Config {
2021-02-22 19:35:27 +00:00
private const _ENVVAR_PREFIX = " TTRSS_ " ;
2021-02-22 18:49:09 +00:00
2021-02-23 13:58:48 +00:00
const T_BOOL = 1 ;
const T_STRING = 2 ;
const T_INT = 3 ;
2022-06-19 18:53:57 +00:00
const SCHEMA_VERSION = 147 ;
2021-03-04 06:22:24 +00:00
2021-11-18 17:51:11 +00:00
/** override default values , defined below in _DEFAULTS [], prefixing with _ENVVAR_PREFIX :
*
* DB_TYPE becomes :
*
* . env ( docker environment ) :
*
* TTRSS_DB_TYPE = pgsql
*
* or config . php :
*
* putenv ( 'TTRSS_DB_TYPE=pgsql' );
*
* note lack of quotes and spaces before and after " = " .
*
2021-03-08 17:39:11 +00:00
*/
2021-02-22 18:49:09 +00:00
2021-11-18 17:51:11 +00:00
/** database type: pgsql or mysql */
2021-02-22 19:35:27 +00:00
const DB_TYPE = " DB_TYPE " ;
2021-11-18 17:51:11 +00:00
/** database server hostname */
2021-02-22 19:35:27 +00:00
const DB_HOST = " DB_HOST " ;
2021-11-18 17:51:11 +00:00
/** database user */
2021-02-22 19:35:27 +00:00
const DB_USER = " DB_USER " ;
2021-11-18 17:51:11 +00:00
/** database name */
2021-02-22 19:35:27 +00:00
const DB_NAME = " DB_NAME " ;
2021-11-18 17:51:11 +00:00
/** database password */
2021-02-22 19:35:27 +00:00
const DB_PASS = " DB_PASS " ;
2021-11-18 17:51:11 +00:00
/** database server port */
2021-02-22 19:35:27 +00:00
const DB_PORT = " DB_PORT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** 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 . */
2021-02-22 19:35:27 +00:00
const MYSQL_CHARSET = " MYSQL_CHARSET " ;
2021-03-08 17:39:11 +00:00
2023-10-22 09:19:05 +00:00
/** this is a fallback falue for the CLI SAPI, it should be set to a fully-qualified tt-rss URL */
2021-02-22 19:35:27 +00:00
const SELF_URL_PATH = " SELF_URL_PATH " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** 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 ) . */
2021-02-22 19:35:27 +00:00
const SINGLE_USER_MODE = " SINGLE_USER_MODE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** 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 . */
2021-02-22 19:35:27 +00:00
const SIMPLE_UPDATE_MODE = " SIMPLE_UPDATE_MODE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** use this PHP CLI executable to start various tasks */
2021-02-22 19:35:27 +00:00
const PHP_EXECUTABLE = " PHP_EXECUTABLE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** base directory for lockfiles (must be writable) */
2021-02-22 19:35:27 +00:00
const LOCK_DIRECTORY = " LOCK_DIRECTORY " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** base directory for local cache (must be writable) */
2021-02-22 19:35:27 +00:00
const CACHE_DIR = " CACHE_DIR " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** directory for feed favicons (directory must be writable) */
2021-02-22 19:35:27 +00:00
const ICONS_DIR = " ICONS_DIR " ;
2021-11-18 17:51:11 +00:00
/** URL for feed favicons */
2021-02-22 19:35:27 +00:00
const ICONS_URL = " ICONS_URL " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** auto create users authenticated via external modules */
2021-02-22 19:35:27 +00:00
const AUTH_AUTO_CREATE = " AUTH_AUTO_CREATE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** auto log in users authenticated via external modules i.e. auth_remote */
2021-02-22 19:35:27 +00:00
const AUTH_AUTO_LOGIN = " AUTH_AUTO_LOGIN " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** unconditinally purge all articles older than this amount , in days
* overrides user - controlled purge interval */
2021-02-22 19:35:27 +00:00
const FORCE_ARTICLE_PURGE = " FORCE_ARTICLE_PURGE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** default lifetime of a session ( e . g . login ) cookie . In seconds ,
* 0 means cookie will be deleted when browser closes . */
2021-02-22 19:35:27 +00:00
const SESSION_COOKIE_LIFETIME = " SESSION_COOKIE_LIFETIME " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** send email using this name */
2021-02-22 19:35:27 +00:00
const SMTP_FROM_NAME = " SMTP_FROM_NAME " ;
2021-11-18 17:51:11 +00:00
/** send email using this address */
2021-02-22 19:35:27 +00:00
const SMTP_FROM_ADDRESS = " SMTP_FROM_ADDRESS " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** default subject for email digest */
2021-02-22 19:35:27 +00:00
const DIGEST_SUBJECT = " DIGEST_SUBJECT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** enable built-in update checker, both for core code and plugins (using git) */
2021-02-22 19:35:27 +00:00
const CHECK_FOR_UPDATES = " CHECK_FOR_UPDATES " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** system plugins enabled for all users , comma separated list , no quotes
* keep at least one auth module in there ( i . e . auth_internal ) */
2021-02-22 19:35:27 +00:00
const PLUGINS = " PLUGINS " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** available options: sql (default, event log), syslog, stdout (for debugging) */
2021-02-22 19:35:27 +00:00
const LOG_DESTINATION = " LOG_DESTINATION " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** link this stylesheet on all pages (if it exists), should be placed in themes.local */
2021-02-22 19:35:27 +00:00
const LOCAL_OVERRIDE_STYLESHEET = " LOCAL_OVERRIDE_STYLESHEET " ;
2021-03-11 04:44:58 +00:00
2021-11-18 17:51:11 +00:00
/** same but this javascript file (you can use that for polyfills), should be placed in themes.local */
2021-03-11 04:44:58 +00:00
const LOCAL_OVERRIDE_JS = " LOCAL_OVERRIDE_JS " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** in seconds, terminate update tasks that ran longer than this interval */
2021-02-22 19:35:27 +00:00
const DAEMON_MAX_CHILD_RUNTIME = " DAEMON_MAX_CHILD_RUNTIME " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** max concurrent update jobs forking update daemon starts */
2021-02-22 19:35:27 +00:00
const DAEMON_MAX_JOBS = " DAEMON_MAX_JOBS " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** How long to wait for response when requesting feed from a site (seconds) */
2021-02-22 19:35:27 +00:00
const FEED_FETCH_TIMEOUT = " FEED_FETCH_TIMEOUT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** How long to wait for response when requesting uncached feed from a site (seconds) */
2021-02-22 19:35:27 +00:00
const FEED_FETCH_NO_CACHE_TIMEOUT = " FEED_FETCH_NO_CACHE_TIMEOUT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** Default timeout when fetching files from remote sites */
2021-02-22 19:35:27 +00:00
const FILE_FETCH_TIMEOUT = " FILE_FETCH_TIMEOUT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** How long to wait for initial response from website when fetching remote files */
2021-02-22 19:35:27 +00:00
const FILE_FETCH_CONNECT_TIMEOUT = " FILE_FETCH_CONNECT_TIMEOUT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** stop updating feeds if user haven't logged in for X days */
2021-02-22 19:35:27 +00:00
const DAEMON_UPDATE_LOGIN_LIMIT = " DAEMON_UPDATE_LOGIN_LIMIT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** how many feeds to update in one batch */
2021-02-22 19:35:27 +00:00
const DAEMON_FEED_LIMIT = " DAEMON_FEED_LIMIT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** default sleep interval between feed updates (sec) */
2021-02-22 19:35:27 +00:00
const DAEMON_SLEEP_INTERVAL = " DAEMON_SLEEP_INTERVAL " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** do not cache files larger than that (bytes) */
2021-02-22 19:35:27 +00:00
const MAX_CACHE_FILE_SIZE = " MAX_CACHE_FILE_SIZE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** do not download files larger than that (bytes) */
2021-02-22 19:35:27 +00:00
const MAX_DOWNLOAD_FILE_SIZE = " MAX_DOWNLOAD_FILE_SIZE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** max file size for downloaded favicons (bytes) */
2021-02-22 19:35:27 +00:00
const MAX_FAVICON_FILE_SIZE = " MAX_FAVICON_FILE_SIZE " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** max age in days for various automatically cached (temporary) files */
2021-02-22 19:35:27 +00:00
const CACHE_MAX_DAYS = " CACHE_MAX_DAYS " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** max interval between forced unconditional updates for servers
* not complying with http if - modified - since ( seconds ) */
2021-02-22 19:35:27 +00:00
const MAX_CONDITIONAL_INTERVAL = " MAX_CONDITIONAL_INTERVAL " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** automatically disable updates for feeds which failed to
* update for this amount of days ; 0 disables */
2021-02-22 19:35:27 +00:00
const DAEMON_UNSUCCESSFUL_DAYS_LIMIT = " DAEMON_UNSUCCESSFUL_DAYS_LIMIT " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** log all sent emails in the event log */
2021-02-22 19:35:27 +00:00
const LOG_SENT_MAIL = " LOG_SENT_MAIL " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** use HTTP proxy for requests */
2021-02-23 06:01:27 +00:00
const HTTP_PROXY = " HTTP_PROXY " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** prevent users from changing passwords */
2021-02-23 06:01:27 +00:00
const FORBID_PASSWORD_CHANGES = " FORBID_PASSWORD_CHANGES " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** default session cookie name */
2021-02-23 14:01:25 +00:00
const SESSION_NAME = " SESSION_NAME " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** enable plugin update checker (using git) */
2021-03-03 16:07:39 +00:00
const CHECK_FOR_PLUGIN_UPDATES = " CHECK_FOR_PLUGIN_UPDATES " ;
2021-03-08 17:39:11 +00:00
2021-11-18 17:51:11 +00:00
/** allow installing first party plugins using plugin installer in prefs */
2021-03-03 16:07:39 +00:00
const ENABLE_PLUGIN_INSTALLER = " ENABLE_PLUGIN_INSTALLER " ;
2021-02-22 18:49:09 +00:00
2021-11-18 17:51:11 +00:00
/** minimum amount of seconds required between authentication attempts */
2021-03-12 06:35:01 +00:00
const AUTH_MIN_INTERVAL = " AUTH_MIN_INTERVAL " ;
2021-11-18 17:51:11 +00:00
/** http user agent (changing this is not recommended) */
2021-08-23 07:56:31 +00:00
const HTTP_USER_AGENT = " HTTP_USER_AGENT " ;
2022-06-09 06:05:36 +00:00
/** delay updates for this feed if received HTTP 429 (Too Many Requests) for this amount of seconds (base value, actual delay is base...base*2) */
const HTTP_429_THROTTLE_INTERVAL = " HTTP_429_THROTTLE_INTERVAL " ;
2023-04-09 18:20:35 +00:00
/** host running Jaeger collector to receive traces (disabled if empty) */
2023-10-20 18:27:10 +00:00
const OPENTELEMETRY_ENDPOINT = " OPENTELEMETRY_ENDPOINT " ;
2023-04-09 18:20:35 +00:00
2023-04-10 04:18:36 +00:00
/** Jaeger service name */
2023-10-20 14:12:29 +00:00
const OPENTELEMETRY_SERVICE = " OPENTELEMETRY_SERVICE " ;
2023-04-10 04:18:36 +00:00
2021-11-18 17:51:11 +00:00
/** default values for all global configuration options */
2021-02-22 19:35:27 +00:00
private const _DEFAULTS = [
2021-02-23 13:58:48 +00:00
Config :: DB_TYPE => [ " pgsql " , Config :: T_STRING ],
Config :: DB_HOST => [ " db " , Config :: T_STRING ],
Config :: DB_USER => [ " " , Config :: T_STRING ],
Config :: DB_NAME => [ " " , Config :: T_STRING ],
Config :: DB_PASS => [ " " , Config :: T_STRING ],
Config :: DB_PORT => [ " 5432 " , Config :: T_STRING ],
Config :: MYSQL_CHARSET => [ " UTF8 " , Config :: T_STRING ],
2023-10-22 09:19:05 +00:00
Config :: SELF_URL_PATH => [ " https://example.com/tt-rss " , Config :: T_STRING ],
2021-02-23 13:58:48 +00:00
Config :: SINGLE_USER_MODE => [ " " , Config :: T_BOOL ],
Config :: SIMPLE_UPDATE_MODE => [ " " , Config :: T_BOOL ],
Config :: PHP_EXECUTABLE => [ " /usr/bin/php " , Config :: T_STRING ],
Config :: LOCK_DIRECTORY => [ " lock " , Config :: T_STRING ],
Config :: CACHE_DIR => [ " cache " , Config :: T_STRING ],
Config :: ICONS_DIR => [ " feed-icons " , Config :: T_STRING ],
Config :: ICONS_URL => [ " feed-icons " , Config :: T_STRING ],
Config :: AUTH_AUTO_CREATE => [ " true " , Config :: T_BOOL ],
Config :: AUTH_AUTO_LOGIN => [ " true " , Config :: T_BOOL ],
Config :: FORCE_ARTICLE_PURGE => [ 0 , Config :: T_INT ],
Config :: SESSION_COOKIE_LIFETIME => [ 86400 , Config :: T_INT ],
Config :: SMTP_FROM_NAME => [ " Tiny Tiny RSS " , Config :: T_STRING ],
Config :: SMTP_FROM_ADDRESS => [ " noreply@localhost " , Config :: T_STRING ],
Config :: DIGEST_SUBJECT => [ " [tt-rss] New headlines for last 24 hours " ,
Config :: T_STRING ],
Config :: CHECK_FOR_UPDATES => [ " true " , Config :: T_BOOL ],
Config :: PLUGINS => [ " auth_internal " , Config :: T_STRING ],
2021-03-07 06:05:23 +00:00
Config :: LOG_DESTINATION => [ Logger :: LOG_DEST_SQL , Config :: T_STRING ],
2021-02-23 13:58:48 +00:00
Config :: LOCAL_OVERRIDE_STYLESHEET => [ " local-overrides.css " ,
Config :: T_STRING ],
2021-03-11 04:44:58 +00:00
Config :: LOCAL_OVERRIDE_JS => [ " local-overrides.js " ,
Config :: T_STRING ],
2021-03-05 17:32:32 +00:00
Config :: DAEMON_MAX_CHILD_RUNTIME => [ 1800 , Config :: T_INT ],
2021-02-23 13:58:48 +00:00
Config :: DAEMON_MAX_JOBS => [ 2 , Config :: T_INT ],
Config :: FEED_FETCH_TIMEOUT => [ 45 , Config :: T_INT ],
Config :: FEED_FETCH_NO_CACHE_TIMEOUT => [ 15 , Config :: T_INT ],
Config :: FILE_FETCH_TIMEOUT => [ 45 , Config :: T_INT ],
Config :: FILE_FETCH_CONNECT_TIMEOUT => [ 15 , Config :: T_INT ],
Config :: DAEMON_UPDATE_LOGIN_LIMIT => [ 30 , Config :: T_INT ],
Config :: DAEMON_FEED_LIMIT => [ 500 , Config :: T_INT ],
Config :: DAEMON_SLEEP_INTERVAL => [ 120 , Config :: T_INT ],
Config :: MAX_CACHE_FILE_SIZE => [ 64 * 1024 * 1024 , Config :: T_INT ],
Config :: MAX_DOWNLOAD_FILE_SIZE => [ 16 * 1024 * 1024 , Config :: T_INT ],
Config :: MAX_FAVICON_FILE_SIZE => [ 1 * 1024 * 1024 , Config :: T_INT ],
Config :: CACHE_MAX_DAYS => [ 7 , Config :: T_INT ],
Config :: MAX_CONDITIONAL_INTERVAL => [ 3600 * 12 , Config :: T_INT ],
Config :: DAEMON_UNSUCCESSFUL_DAYS_LIMIT => [ 30 , Config :: T_INT ],
Config :: LOG_SENT_MAIL => [ " " , Config :: T_BOOL ],
Config :: HTTP_PROXY => [ " " , Config :: T_STRING ],
Config :: FORBID_PASSWORD_CHANGES => [ " " , Config :: T_BOOL ],
2021-02-23 14:01:25 +00:00
Config :: SESSION_NAME => [ " ttrss_sid " , Config :: T_STRING ],
2021-03-03 16:07:39 +00:00
Config :: CHECK_FOR_PLUGIN_UPDATES => [ " true " , Config :: T_BOOL ],
Config :: ENABLE_PLUGIN_INSTALLER => [ " true " , Config :: T_BOOL ],
2021-03-12 06:35:01 +00:00
Config :: AUTH_MIN_INTERVAL => [ 5 , Config :: T_INT ],
2021-08-23 07:56:31 +00:00
Config :: HTTP_USER_AGENT => [ 'Tiny Tiny RSS/%s (https://tt-rss.org/)' ,
Config :: T_STRING ],
2022-06-09 06:05:36 +00:00
Config :: HTTP_429_THROTTLE_INTERVAL => [ 3600 , Config :: T_INT ],
2023-10-20 18:27:10 +00:00
Config :: OPENTELEMETRY_ENDPOINT => [ " " , Config :: T_STRING ],
2023-10-20 14:12:29 +00:00
Config :: OPENTELEMETRY_SERVICE => [ " tt-rss " , Config :: T_STRING ],
2021-02-22 19:35:27 +00:00
];
2021-02-22 18:49:09 +00:00
2021-11-10 21:38:25 +00:00
/** @var Config|null */
2021-02-22 19:35:27 +00:00
private static $instance ;
2021-02-22 18:49:09 +00:00
2021-11-10 21:38:25 +00:00
/** @var array<string, array<bool|int|string>> */
2021-02-22 19:35:27 +00:00
private $params = [];
2021-11-10 21:38:25 +00:00
/** @var array<string, mixed> */
2021-03-01 10:43:37 +00:00
private $version = [];
2021-02-22 19:35:27 +00:00
2021-11-01 16:03:28 +00:00
/** @var Db_Migrations|null $migrations */
2021-03-04 05:30:52 +00:00
private $migrations ;
2023-10-23 20:10:17 +00:00
private static $self_url_path_strip_dirs = 0 ;
2021-02-25 18:42:05 +00:00
public static function get_instance () : Config {
2021-02-22 18:49:09 +00:00
if ( self :: $instance == null )
self :: $instance = new self ();
return self :: $instance ;
}
2021-02-25 12:49:30 +00:00
private function __clone () {
//
}
2021-02-22 19:35:27 +00:00
function __construct () {
$ref = new ReflectionClass ( get_class ( $this ));
2021-02-22 18:49:09 +00:00
2021-02-22 19:35:27 +00:00
foreach ( $ref -> getConstants () as $const => $cvalue ) {
2021-11-01 16:03:28 +00:00
if ( isset ( self :: _DEFAULTS [ $const ])) {
$override = getenv ( self :: _ENVVAR_PREFIX . $const );
2021-02-22 18:49:09 +00:00
2021-11-01 16:03:28 +00:00
list ( $defval , $deftype ) = self :: _DEFAULTS [ $const ];
2021-02-23 13:58:48 +00:00
2021-03-07 06:00:36 +00:00
$this -> params [ $cvalue ] = [ self :: cast_to ( $override !== false ? $override : $defval , $deftype ), $deftype ];
2021-02-22 19:35:27 +00:00
}
}
}
2021-02-22 18:49:09 +00:00
2021-11-18 17:51:11 +00:00
/** determine tt - rss version ( using git )
*
* package maintainers who don ' t use git : if version_static . txt exists in tt - rss root
* directory , its contents are displayed instead of git commit - based version , this could be generated
* based on source git tree commit used when creating the package
2021-11-10 21:38:25 +00:00
* @ return array < string , mixed >| string
*/
2021-03-01 10:43:37 +00:00
static function get_version ( bool $as_string = true ) {
return self :: get_instance () -> _get_version ( $as_string );
}
2022-12-30 06:46:01 +00:00
// returns version showing (if possible) full timestamp of commit id
static function get_version_html () : string {
$version = self :: get_version ( false );
2023-04-10 17:04:38 +00:00
return sprintf ( " <span title= \" %s \n %s \n %s \" >%s</span> " ,
date ( " Y-m-d H:i:s " , ( $version [ 'timestamp' ] ? ? 0 )),
2023-05-27 02:58:11 +00:00
$version [ 'commit' ] ? ? '' ,
2023-04-10 17:04:38 +00:00
$version [ 'branch' ] ? ? '' ,
$version [ 'version' ]);
2022-12-30 06:46:01 +00:00
}
2021-11-10 21:38:25 +00:00
/**
* @ return array < string , mixed >| string
*/
2021-03-01 10:43:37 +00:00
private function _get_version ( bool $as_string = true ) {
$root_dir = dirname ( __DIR__ );
if ( empty ( $this -> version )) {
$this -> version [ " status " ] = - 1 ;
2023-04-10 17:06:52 +00:00
if ( getenv ( " CI_COMMIT_SHORT_SHA " ) && getenv ( " CI_COMMIT_TIMESTAMP " )) {
2023-04-10 15:05:13 +00:00
2023-04-10 17:04:38 +00:00
$this -> version [ " branch " ] = getenv ( " CI_COMMIT_BRANCH " );
2023-04-10 16:53:49 +00:00
$this -> version [ " timestamp " ] = strtotime ( getenv ( " CI_COMMIT_TIMESTAMP " ));
2023-04-10 17:11:26 +00:00
$this -> version [ " version " ] = sprintf ( " %s-%s " , date ( " y.m " , $this -> version [ " timestamp " ]), getenv ( " CI_COMMIT_SHORT_SHA " ));
2023-04-10 16:55:20 +00:00
$this -> version [ " commit " ] = getenv ( " CI_COMMIT_SHORT_SHA " );
2023-04-10 15:05:13 +00:00
$this -> version [ " status " ] = 0 ;
} else if ( PHP_OS === " Darwin " ) {
2023-04-06 18:36:36 +00:00
$this -> version [ " version " ] = " UNKNOWN (Unsupported, Darwin) " ;
2021-03-01 10:43:37 +00:00
} else if ( file_exists ( " $root_dir /version_static.txt " )) {
$this -> version [ " version " ] = trim ( file_get_contents ( " $root_dir /version_static.txt " )) . " (Unsupported) " ;
2021-11-23 11:44:48 +00:00
} else if ( ini_get ( " open_basedir " )) {
2021-12-02 17:55:08 +00:00
$this -> version [ " version " ] = " UNKNOWN (Unsupported, open_basedir) " ;
2021-03-01 10:43:37 +00:00
} else if ( is_dir ( " $root_dir /.git " )) {
$this -> version = self :: get_version_from_git ( $root_dir );
if ( $this -> version [ " status " ] != 0 ) {
user_error ( " Unable to determine version: " . $this -> version [ " version " ], E_USER_WARNING );
$this -> version [ " version " ] = " UNKNOWN (Unsupported, Git error) " ;
2021-11-23 11:44:48 +00:00
} else if ( ! getenv ( " SCRIPT_ROOT " ) || ! file_exists ( " /.dockerenv " )) {
2021-11-17 07:36:04 +00:00
$this -> version [ " version " ] .= " (Unsupported) " ;
2021-03-01 10:43:37 +00:00
}
2021-11-17 07:36:04 +00:00
2021-03-01 10:43:37 +00:00
} else {
$this -> version [ " version " ] = " UNKNOWN (Unsupported) " ;
}
}
return $as_string ? $this -> version [ " version " ] : $this -> version ;
}
2021-11-10 21:38:25 +00:00
/**
* @ return array < string , int | string >
*/
static function get_version_from_git ( string $dir ) : array {
2021-03-01 10:43:37 +00:00
$descriptorspec = [
1 => [ " pipe " , " w " ], // STDOUT
2 => [ " pipe " , " w " ], // STDERR
];
$rv = [
" status " => - 1 ,
" version " => " " ,
2023-04-10 17:04:38 +00:00
" branch " => " " ,
2021-03-01 10:43:37 +00:00
" commit " => " " ,
" timestamp " => 0 ,
];
2021-03-02 05:33:56 +00:00
$proc = proc_open ( " git --no-pager log --pretty= \" version-%ct-%h \" -n1 HEAD " ,
2021-03-01 10:43:37 +00:00
$descriptorspec , $pipes , $dir );
if ( is_resource ( $proc )) {
$stdout = trim ( stream_get_contents ( $pipes [ 1 ]));
$stderr = trim ( stream_get_contents ( $pipes [ 2 ]));
$status = proc_close ( $proc );
$rv [ " status " ] = $status ;
2021-03-02 05:33:56 +00:00
list ( $check , $timestamp , $commit ) = explode ( " - " , $stdout );
if ( $check == " version " ) {
2021-03-01 10:43:37 +00:00
2022-12-30 06:46:01 +00:00
$rv [ " version " ] = sprintf ( " %s-%s " , date ( " y.m " , ( int ) $timestamp ), $commit );
2021-03-01 10:43:37 +00:00
$rv [ " commit " ] = $commit ;
$rv [ " timestamp " ] = $timestamp ;
2021-03-02 05:33:56 +00:00
// proc_close() may return -1 even if command completed successfully
// so if it looks like we got valid data, we ignore it
if ( $rv [ " status " ] == - 1 )
$rv [ " status " ] = 0 ;
2021-03-01 10:43:37 +00:00
} else {
$rv [ " version " ] = T_sprintf ( " Git error [RC=%d]: %s " , $status , $stderr );
}
}
return $rv ;
}
2021-03-04 05:30:52 +00:00
static function get_migrations () : Db_Migrations {
return self :: get_instance () -> _get_migrations ();
2021-02-25 18:42:05 +00:00
}
2021-03-04 05:30:52 +00:00
private function _get_migrations () : Db_Migrations {
if ( empty ( $this -> migrations )) {
$this -> migrations = new Db_Migrations ();
2021-03-04 06:22:24 +00:00
$this -> migrations -> initialize ( dirname ( __DIR__ ) . " /sql " , " ttrss_version " , true , self :: SCHEMA_VERSION );
2021-02-25 18:42:05 +00:00
}
2021-03-04 05:30:52 +00:00
return $this -> migrations ;
}
static function is_migration_needed () : bool {
return self :: get_migrations () -> is_migration_needed ();
}
static function get_schema_version () : int {
return self :: get_migrations () -> get_version ();
2021-02-25 18:42:05 +00:00
}
2021-11-10 21:38:25 +00:00
/**
* @ return bool | int | string
*/
2021-02-25 06:33:36 +00:00
static function cast_to ( string $value , int $type_hint ) {
2021-02-23 13:58:48 +00:00
switch ( $type_hint ) {
case self :: T_BOOL :
return sql_bool_to_bool ( $value );
case self :: T_INT :
return ( int ) $value ;
default :
return $value ;
}
}
2021-11-10 21:38:25 +00:00
/**
* @ return bool | int | string
*/
2021-02-22 20:20:52 +00:00
private function _get ( string $param ) {
2021-02-23 13:58:48 +00:00
list ( $value , $type_hint ) = $this -> params [ $param ];
return $this -> cast_to ( $value , $type_hint );
2021-02-22 19:35:27 +00:00
}
2021-02-22 18:49:09 +00:00
2021-11-10 21:38:25 +00:00
private function _add ( string $param , string $default , int $type_hint ) : void {
2021-11-01 16:03:28 +00:00
$override = getenv ( self :: _ENVVAR_PREFIX . $param );
2021-02-22 20:20:52 +00:00
2021-03-07 06:00:36 +00:00
$this -> params [ $param ] = [ self :: cast_to ( $override !== false ? $override : $default , $type_hint ), $type_hint ];
2021-02-22 20:20:52 +00:00
}
2021-11-10 21:38:25 +00:00
static function add ( string $param , string $default , int $type_hint = Config :: T_STRING ) : void {
2021-02-22 20:20:52 +00:00
$instance = self :: get_instance ();
2021-11-10 21:38:25 +00:00
$instance -> _add ( $param , $default , $type_hint );
2021-02-22 20:20:52 +00:00
}
2021-11-10 21:38:25 +00:00
/**
* @ return bool | int | string
*/
2021-02-22 20:20:52 +00:00
static function get ( string $param ) {
2021-02-22 19:35:27 +00:00
$instance = self :: get_instance ();
2021-02-22 18:49:09 +00:00
2021-02-22 19:35:27 +00:00
return $instance -> _get ( $param );
}
2021-02-22 18:49:09 +00:00
2021-03-03 08:23:39 +00:00
static function is_server_https () : bool {
2021-03-01 07:20:21 +00:00
return ( ! empty ( $_SERVER [ 'HTTPS' ]) && ( $_SERVER [ 'HTTPS' ] != 'off' )) ||
( ! empty ( $_SERVER [ 'HTTP_X_FORWARDED_PROTO' ]) && $_SERVER [ 'HTTP_X_FORWARDED_PROTO' ] == 'https' );
}
2023-10-23 20:10:17 +00:00
static function strip_self_url_path_dirs ( $amount ) {
self :: $self_url_path_strip_dirs = $amount ;
}
2023-10-22 09:19:05 +00:00
/** returns fully - qualified external URL to tt - rss ( no trailing slash )
* SELF_URL_PATH configuration variable is used as a fallback for the CLI SAPI
* */
static function get_self_url () : string {
if ( php_sapi_name () == " cli " ) {
return self :: get ( Config :: SELF_URL_PATH );
} else {
$proto = self :: is_server_https () ? 'https' : 'http' ;
2021-03-01 07:20:21 +00:00
2023-10-22 09:19:05 +00:00
$self_url_path = $proto . '://' . $_SERVER [ " HTTP_HOST " ] . parse_url ( $_SERVER [ " REQUEST_URI " ], PHP_URL_PATH );
$self_url_path = preg_replace ( " / \ w+ \ .php( \ ?.* $ )? $ / " , " " , $self_url_path );
2021-03-03 08:35:04 +00:00
2023-10-23 20:10:17 +00:00
for ( $i = 0 ; $i < self :: $self_url_path_strip_dirs ; $i ++ )
$self_url_path = dirname ( $self_url_path );
2023-10-22 09:19:05 +00:00
if ( substr ( $self_url_path , - 1 ) === " / " ) {
return substr ( $self_url_path , 0 , - 1 );
} else {
return $self_url_path ;
}
2021-03-03 08:23:39 +00:00
}
2021-03-01 07:20:21 +00:00
}
/* sanity check stuff */
2021-11-18 17:51:11 +00:00
/** checks for mysql tables not using InnoDB ( tt - rss is incompatible with MyISAM )
2021-11-10 21:38:25 +00:00
* @ return array < int , array < string , string >> A list of entries identifying tt - rss tables with bad config
*/
2021-03-01 07:20:21 +00:00
private static function check_mysql_tables () {
$pdo = Db :: pdo ();
$sth = $pdo -> prepare ( " SELECT engine, table_name FROM information_schema.tables WHERE
table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB' " );
$sth -> execute ([ self :: get ( Config :: DB_NAME )]);
$bad_tables = [];
while ( $line = $sth -> fetch ()) {
array_push ( $bad_tables , $line );
}
return $bad_tables ;
}
2021-11-10 21:38:25 +00:00
static function sanity_check () : void {
2021-03-01 07:20:21 +00:00
2021-03-02 14:12:35 +00:00
/*
we don 't actually need the DB object right now but some checks below might use ORM which won' t be initialized
because it is set up in the Db constructor , which is why it ' s a good idea to invoke it as early as possible
it is a bit of a hack , maybe ORM should be initialized somewhere else ( functions . php ? )
*/
2021-03-02 14:11:38 +00:00
$pdo = Db :: pdo ();
2021-03-04 06:22:24 +00:00
$errors = [];
2021-03-01 07:20:21 +00:00
if ( strpos ( self :: get ( Config :: PLUGINS ), " auth_ " ) === false ) {
array_push ( $errors , " Please enable at least one authentication module via PLUGINS " );
}
2022-04-03 16:39:34 +00:00
/* we assume our dependencies are sane under docker , so some sanity checks are skipped .
this also allows tt - rss process to run under root if requested ( I ' m using this for development
under podman because of uidmapping issues with rootless containers , don ' t use in production - fox ) */
if ( ! getenv ( " container " )) {
if ( function_exists ( 'posix_getuid' ) && posix_getuid () == 0 ) {
array_push ( $errors , " Please don't run this script as root. " );
}
2021-03-01 07:20:21 +00:00
2022-07-29 03:34:20 +00:00
if ( version_compare ( PHP_VERSION , '7.4.0' , '<' )) {
array_push ( $errors , " PHP version 7.4.0 or newer required. You're using " . PHP_VERSION . " . " );
2022-04-03 16:39:34 +00:00
}
if ( ! class_exists ( " UConverter " )) {
array_push ( $errors , " PHP UConverter class is missing, it's provided by the Internationalization (intl) module. " );
}
if ( ! function_exists ( " curl_init " ) && ! ini_get ( " allow_url_fopen " )) {
array_push ( $errors , " PHP configuration option allow_url_fopen is disabled, and CURL functions are not present. Either enable allow_url_fopen or install PHP extension for CURL. " );
}
if ( ! function_exists ( " json_encode " )) {
array_push ( $errors , " PHP support for JSON is required, but was not found. " );
}
2023-02-25 16:30:41 +00:00
if ( ! function_exists ( " flock " )) {
array_push ( $errors , " PHP support for flock() function is required. " );
}
2022-04-03 16:39:34 +00:00
if ( ! class_exists ( " PDO " )) {
array_push ( $errors , " PHP support for PDO is required but was not found. " );
}
if ( ! function_exists ( " mb_strlen " )) {
array_push ( $errors , " PHP support for mbstring functions is required but was not found. " );
}
2021-03-01 07:20:21 +00:00
2022-04-03 16:39:34 +00:00
if ( ! function_exists ( " hash " )) {
array_push ( $errors , " PHP support for hash() function is required but was not found. " );
}
if ( ini_get ( " safe_mode " )) {
array_push ( $errors , " PHP safe mode setting is obsolete and not supported by tt-rss. " );
}
if ( ! function_exists ( " mime_content_type " )) {
array_push ( $errors , " PHP function mime_content_type() is missing, try enabling fileinfo module. " );
}
if ( ! class_exists ( " DOMDocument " )) {
array_push ( $errors , " PHP support for DOMDocument is required, but was not found. " );
}
2021-03-01 07:20:21 +00:00
}
if ( ! is_writable ( self :: get ( Config :: CACHE_DIR ) . " /images " )) {
array_push ( $errors , " Image cache is not writable (chmod -R 777 " . self :: get ( Config :: CACHE_DIR ) . " /images) " );
}
if ( ! is_writable ( self :: get ( Config :: CACHE_DIR ) . " /upload " )) {
array_push ( $errors , " Upload cache is not writable (chmod -R 777 " . self :: get ( Config :: CACHE_DIR ) . " /upload) " );
}
if ( ! is_writable ( self :: get ( Config :: CACHE_DIR ) . " /export " )) {
array_push ( $errors , " Data export cache is not writable (chmod -R 777 " . self :: get ( Config :: CACHE_DIR ) . " /export) " );
}
2022-04-03 16:39:34 +00:00
if ( ! is_writable ( self :: get ( Config :: ICONS_DIR ))) {
array_push ( $errors , " ICONS_DIR defined in config.php is not writable (chmod -R 777 " . self :: get ( Config :: ICONS_DIR ) . " ). \n " );
}
if ( ! is_writable ( self :: get ( Config :: LOCK_DIRECTORY ))) {
array_push ( $errors , " LOCK_DIRECTORY is not writable (chmod -R 777 " . self :: get ( Config :: LOCK_DIRECTORY ) . " ). \n " );
}
2021-05-11 16:36:25 +00:00
// ttrss_users won't be there on initial startup (before migrations are done)
2021-05-11 16:37:31 +00:00
if ( ! Config :: is_migration_needed () && self :: get ( Config :: SINGLE_USER_MODE )) {
2021-03-01 07:20:21 +00:00
if ( UserHelper :: get_login_by_id ( 1 ) != " admin " ) {
array_push ( $errors , " SINGLE_USER_MODE is enabled but default admin account (ID: 1) is not found. " );
}
}
2022-04-03 16:39:34 +00:00
// skip check for CLI scripts so that we could install database schema if it is missing.
2021-03-01 07:20:21 +00:00
if ( php_sapi_name () != " cli " ) {
2021-03-04 06:22:24 +00:00
if ( self :: get_schema_version () < 0 ) {
array_push ( $errors , " Base database schema is missing. Either load it manually or perform a migration (<code>update.php --update-schema</code>) " );
}
2021-03-01 07:20:21 +00:00
}
if ( self :: get ( Config :: DB_TYPE ) == " mysql " ) {
$bad_tables = self :: check_mysql_tables ();
if ( count ( $bad_tables ) > 0 ) {
$bad_tables_fmt = [];
foreach ( $bad_tables as $bt ) {
array_push ( $bad_tables_fmt , sprintf ( " %s (%s) " , $bt [ 'table_name' ], $bt [ 'engine' ]));
}
$msg = " <p>The following tables use an unsupported MySQL engine: <b> " .
implode ( " , " , $bad_tables_fmt ) . " </b>.</p> " ;
$msg .= " <p>The only supported engine on MySQL is InnoDB. MyISAM lacks functionality to run
tt - rss .
Please backup your data ( via OPML ) and re - import the schema before continuing .</ p >
< p >< b > WARNING : importing the schema would mean LOSS OF ALL YOUR DATA .</ b ></ p > " ;
array_push ( $errors , $msg );
}
}
if ( count ( $errors ) > 0 && php_sapi_name () != " cli " ) { ?>
<! DOCTYPE html >
< html >
< head >
< title > Startup failed </ title >
< meta http - equiv = " Content-Type " content = " text/html; charset=utf-8 " >
< link rel = " stylesheet " type = " text/css " href = " themes/light.css " >
</ head >
< body class = " sanity_failed flat ttrss_utility " >
< div class = " content " >
< h1 > Startup failed </ h1 >
< p > Please fix errors indicated by the following messages :</ p >
< ? php foreach ( $errors as $error ) { echo self :: format_error ( $error ); } ?>
< p > You might want to check tt - rss < a target = " _blank " href = " https://tt-rss.org/wiki.php " > wiki </ a > or the
< a target = " _blank " href = " https://community.tt-rss.org/ " > forums </ a > for more information . Please search the forums before creating new topic
for your question .</ p >
</ div >
</ body >
</ html >
< ? php
die ;
} else if ( count ( $errors ) > 0 ) {
echo " Please fix errors indicated by the following messages: \n \n " ;
foreach ( $errors as $error ) {
echo " * " . strip_tags ( $error ) . " \n " ;
}
echo " \n You might want to check tt-rss wiki or the forums for more information. \n " ;
echo " Please search the forums before creating new topic for your question. \n " ;
exit ( 1 );
}
}
2021-11-10 21:38:25 +00:00
private static function format_error ( string $msg ) : string {
2021-03-01 07:20:21 +00:00
return " <div class= \" alert alert-danger \" > $msg </div> " ;
}
2021-03-11 04:44:58 +00:00
2021-11-10 21:38:25 +00:00
static function get_override_links () : string {
2021-03-11 04:44:58 +00:00
$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 );
2021-03-12 04:40:34 +00:00
return $rv ;
2021-03-11 04:44:58 +00:00
}
2021-08-23 07:56:31 +00:00
2021-11-10 21:38:25 +00:00
static function get_user_agent () : string {
2021-08-23 07:56:31 +00:00
return sprintf ( self :: get ( self :: HTTP_USER_AGENT ), self :: get_version ());
}
2021-02-22 20:20:52 +00:00
}