diff options
Diffstat (limited to 'lib/Configuration.php')
-rw-r--r-- | lib/Configuration.php | 610 |
1 files changed, 317 insertions, 293 deletions
diff --git a/lib/Configuration.php b/lib/Configuration.php index e97e7d6b..ce01b7df 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -1,4 +1,5 @@ <?php + /** * This file is part of RSS-Bridge, a PHP project capable of generating RSS and * Atom feeds for websites that don't have one. @@ -6,9 +7,9 @@ * For the full license information, please view the UNLICENSE file distributed * with this source code. * - * @package Core - * @license http://unlicense.org/ UNLICENSE - * @link https://github.com/rss-bridge/rss-bridge + * @package Core + * @license http://unlicense.org/ UNLICENSE + * @link https://github.com/rss-bridge/rss-bridge */ /** @@ -16,294 +17,317 @@ * * This class implements a configuration module for RSS-Bridge. */ -final class Configuration { - - /** - * Holds the current release version of RSS-Bridge. - * - * Do not access this property directly! - * Use {@see Configuration::getVersion()} instead. - * - * @var string - * - * @todo Replace this property by a constant. - */ - public static $VERSION = 'dev.2022-06-14'; - - /** - * Holds the configuration data. - * - * Do not access this property directly! - * Use {@see Configuration::getConfig()} instead. - * - * @var array|null - */ - private static $config = null; - - /** - * Throw an exception when trying to create a new instance of this class. - * - * @throws \LogicException if called. - */ - public function __construct(){ - throw new \LogicException('Can\'t create object of this class!'); - } - - /** - * Verifies the current installation of RSS-Bridge and PHP. - * - * Returns an error message and aborts execution if the installation does - * not satisfy the requirements of RSS-Bridge. - * - * **Requirements** - * - PHP 7.1.0 or higher - * - `openssl` extension - * - `libxml` extension - * - `mbstring` extension - * - `simplexml` extension - * - `curl` extension - * - `json` extension - * - The cache folder specified by {@see PATH_CACHE} requires write permission - * - The whitelist file specified by {@see WHITELIST} requires write permission - * - * @link http://php.net/supported-versions.php PHP Supported Versions - * @link http://php.net/manual/en/book.openssl.php OpenSSL - * @link http://php.net/manual/en/book.libxml.php libxml - * @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring) - * @link http://php.net/manual/en/book.simplexml.php SimpleXML - * @link http://php.net/manual/en/book.curl.php Client URL Library (curl) - * @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json) - * - * @return void - */ - public static function verifyInstallation() { - - // Check PHP version - if(version_compare(PHP_VERSION, '7.4.0') === -1) { - self::reportError('RSS-Bridge requires at least PHP version 7.4.0!'); - } - // extensions check - if(!extension_loaded('openssl')) - self::reportError('"openssl" extension not loaded. Please check "php.ini"'); - - if(!extension_loaded('libxml')) - self::reportError('"libxml" extension not loaded. Please check "php.ini"'); - - if(!extension_loaded('mbstring')) - self::reportError('"mbstring" extension not loaded. Please check "php.ini"'); - - if(!extension_loaded('simplexml')) - self::reportError('"simplexml" extension not loaded. Please check "php.ini"'); - - // Allow RSS-Bridge to run without curl module in CLI mode without root certificates - if(!extension_loaded('curl') && !(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo')))) - self::reportError('"curl" extension not loaded. Please check "php.ini"'); - - if(!extension_loaded('json')) - self::reportError('"json" extension not loaded. Please check "php.ini"'); - - } - - /** - * Loads the configuration from disk and checks if the parameters are valid. - * - * Returns an error message and aborts execution if the configuration is invalid. - * - * The RSS-Bridge configuration is split into two files: - * - {@see FILE_CONFIG_DEFAULT} The default configuration file that ships - * with every release of RSS-Bridge (do not modify this file!). - * - {@see FILE_CONFIG} The local configuration file that can be modified - * by server administrators. - * - * RSS-Bridge will first load {@see FILE_CONFIG_DEFAULT} into memory and then - * replace parameters with the contents of {@see FILE_CONFIG}. That way new - * parameters are automatically initialized with default values and custom - * configurations can be reduced to the minimum set of parametes necessary - * (only the ones that changed). - * - * The configuration files must be placed in the root folder of RSS-Bridge - * (next to `index.php`). - * - * _Notice_: The configuration is stored in {@see Configuration::$config}. - * - * @return void - */ - public static function loadConfiguration() { - - if(!file_exists(FILE_CONFIG_DEFAULT)) - self::reportError('The default configuration file is missing at ' . FILE_CONFIG_DEFAULT); - - Configuration::$config = parse_ini_file(FILE_CONFIG_DEFAULT, true, INI_SCANNER_TYPED); - if(!Configuration::$config) - self::reportError('Error parsing ' . FILE_CONFIG_DEFAULT); - - if(file_exists(FILE_CONFIG)) { - // Replace default configuration with custom settings - foreach(parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) { - foreach($section as $key => $value) { - Configuration::$config[$header][$key] = $value; - } - } - } - - foreach (getenv() as $envkey => $value) { - // Replace all settings with their respective environment variable if available - $keyArray = explode('_', $envkey); - if($keyArray[0] === 'RSSBRIDGE') { - $header = strtolower($keyArray[1]); - $key = strtolower($keyArray[2]); - if($value === 'true' || $value === 'false') { - $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); - } - Configuration::$config[$header][$key] = $value; - } - } - - if(!is_string(self::getConfig('system', 'timezone')) - || !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC))) - self::reportConfigurationError('system', 'timezone'); - - date_default_timezone_set(self::getConfig('system', 'timezone')); - - if(!is_string(self::getConfig('proxy', 'url'))) - self::reportConfigurationError('proxy', 'url', 'Is not a valid string'); - - if(!empty(self::getConfig('proxy', 'url'))) { - /** URL of the proxy server */ - define('PROXY_URL', self::getConfig('proxy', 'url')); - } - - if(!is_bool(self::getConfig('proxy', 'by_bridge'))) - self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean'); - - /** True if proxy usage can be enabled selectively for each bridge */ - define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge')); - - if(!is_string(self::getConfig('proxy', 'name'))) - self::reportConfigurationError('proxy', 'name', 'Is not a valid string'); - - /** Name of the proxy server */ - define('PROXY_NAME', self::getConfig('proxy', 'name')); - - if(!is_string(self::getConfig('cache', 'type'))) - self::reportConfigurationError('cache', 'type', 'Is not a valid string'); - - if(!is_bool(self::getConfig('cache', 'custom_timeout'))) - self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean'); - - /** True if the cache timeout can be specified by the user */ - define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout')); - - if(!is_bool(self::getConfig('authentication', 'enable'))) - self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean'); - - if(!is_string(self::getConfig('authentication', 'username'))) - self::reportConfigurationError('authentication', 'username', 'Is not a valid string'); - - if(!is_string(self::getConfig('authentication', 'password'))) - self::reportConfigurationError('authentication', 'password', 'Is not a valid string'); - - if(!empty(self::getConfig('admin', 'email')) - && !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL)) - self::reportConfigurationError('admin', 'email', 'Is not a valid email address'); - - if(!is_bool(self::getConfig('admin', 'donations'))) - self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean'); - - if(!is_string(self::getConfig('error', 'output'))) - self::reportConfigurationError('error', 'output', 'Is not a valid String'); - - if(!is_numeric(self::getConfig('error', 'report_limit')) - || self::getConfig('error', 'report_limit') < 1) - self::reportConfigurationError('admin', 'report_limit', 'Value is invalid'); - - } - - /** - * Returns the value of a parameter identified by section and key. - * - * @param string $section The section name. - * @param string $key The property name (key). - * @return mixed|null The parameter value. - */ - public static function getConfig($section, $key) { - if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) { - return self::$config[$section][$key]; - } - - return null; - } - - /** - * Returns the current version string of RSS-Bridge. - * - * This function returns the contents of {@see Configuration::$VERSION} for - * regular installations and the git branch name and commit id for instances - * running in a git environment. - * - * @return string The version string. - */ - public static function getVersion() { - - $headFile = PATH_ROOT . '.git/HEAD'; - - // '@' is used to mute open_basedir warning - if(@is_readable($headFile)) { - - $revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1); - $parts = explode('/', $revisionHashFile); - - if(isset($parts[3])) { - $branchName = $parts[3]; - if(file_exists($revisionHashFile)) { - return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7); - } - } - } - - return Configuration::$VERSION; - - } - - /** - * Reports an configuration error for the specified section and key to the - * user and ends execution - * - * @param string $section The section name - * @param string $key The configuration key - * @param string $message An optional message to the user - * - * @return void - */ - private static function reportConfigurationError($section, $key, $message = '') { - - $report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL; - - if(file_exists(FILE_CONFIG)) { - $report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL; - } elseif(!file_exists(FILE_CONFIG_DEFAULT)) { - $report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL; - } else { - $report .= 'The default configuration file is broken.' . PHP_EOL - . 'Restore the original file from ' . REPOSITORY . PHP_EOL; - } - - $report .= $message; - self::reportError($report); - - } - - /** - * Reports an error message to the user and ends execution - * - * @param string $message The error message - * - * @return void - */ - private static function reportError($message) { - - header('Content-Type: text/plain', true, 500); - die('Configuration error' . PHP_EOL . $message); - - } +final class Configuration +{ + /** + * Holds the current release version of RSS-Bridge. + * + * Do not access this property directly! + * Use {@see Configuration::getVersion()} instead. + * + * @var string + * + * @todo Replace this property by a constant. + */ + public static $VERSION = 'dev.2022-06-14'; + + /** + * Holds the configuration data. + * + * Do not access this property directly! + * Use {@see Configuration::getConfig()} instead. + * + * @var array|null + */ + private static $config = null; + + /** + * Throw an exception when trying to create a new instance of this class. + * + * @throws \LogicException if called. + */ + public function __construct() + { + throw new \LogicException('Can\'t create object of this class!'); + } + + /** + * Verifies the current installation of RSS-Bridge and PHP. + * + * Returns an error message and aborts execution if the installation does + * not satisfy the requirements of RSS-Bridge. + * + * **Requirements** + * - PHP 7.1.0 or higher + * - `openssl` extension + * - `libxml` extension + * - `mbstring` extension + * - `simplexml` extension + * - `curl` extension + * - `json` extension + * - The cache folder specified by {@see PATH_CACHE} requires write permission + * - The whitelist file specified by {@see WHITELIST} requires write permission + * + * @link http://php.net/supported-versions.php PHP Supported Versions + * @link http://php.net/manual/en/book.openssl.php OpenSSL + * @link http://php.net/manual/en/book.libxml.php libxml + * @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring) + * @link http://php.net/manual/en/book.simplexml.php SimpleXML + * @link http://php.net/manual/en/book.curl.php Client URL Library (curl) + * @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json) + * + * @return void + */ + public static function verifyInstallation() + { + // Check PHP version + if (version_compare(PHP_VERSION, '7.4.0') === -1) { + self::reportError('RSS-Bridge requires at least PHP version 7.4.0!'); + } + // extensions check + if (!extension_loaded('openssl')) { + self::reportError('"openssl" extension not loaded. Please check "php.ini"'); + } + + if (!extension_loaded('libxml')) { + self::reportError('"libxml" extension not loaded. Please check "php.ini"'); + } + + if (!extension_loaded('mbstring')) { + self::reportError('"mbstring" extension not loaded. Please check "php.ini"'); + } + + if (!extension_loaded('simplexml')) { + self::reportError('"simplexml" extension not loaded. Please check "php.ini"'); + } + + // Allow RSS-Bridge to run without curl module in CLI mode without root certificates + if (!extension_loaded('curl') && !(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo')))) { + self::reportError('"curl" extension not loaded. Please check "php.ini"'); + } + + if (!extension_loaded('json')) { + self::reportError('"json" extension not loaded. Please check "php.ini"'); + } + } + + /** + * Loads the configuration from disk and checks if the parameters are valid. + * + * Returns an error message and aborts execution if the configuration is invalid. + * + * The RSS-Bridge configuration is split into two files: + * - {@see FILE_CONFIG_DEFAULT} The default configuration file that ships + * with every release of RSS-Bridge (do not modify this file!). + * - {@see FILE_CONFIG} The local configuration file that can be modified + * by server administrators. + * + * RSS-Bridge will first load {@see FILE_CONFIG_DEFAULT} into memory and then + * replace parameters with the contents of {@see FILE_CONFIG}. That way new + * parameters are automatically initialized with default values and custom + * configurations can be reduced to the minimum set of parametes necessary + * (only the ones that changed). + * + * The configuration files must be placed in the root folder of RSS-Bridge + * (next to `index.php`). + * + * _Notice_: The configuration is stored in {@see Configuration::$config}. + * + * @return void + */ + public static function loadConfiguration() + { + if (!file_exists(FILE_CONFIG_DEFAULT)) { + self::reportError('The default configuration file is missing at ' . FILE_CONFIG_DEFAULT); + } + + Configuration::$config = parse_ini_file(FILE_CONFIG_DEFAULT, true, INI_SCANNER_TYPED); + if (!Configuration::$config) { + self::reportError('Error parsing ' . FILE_CONFIG_DEFAULT); + } + + if (file_exists(FILE_CONFIG)) { + // Replace default configuration with custom settings + foreach (parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) { + foreach ($section as $key => $value) { + Configuration::$config[$header][$key] = $value; + } + } + } + + foreach (getenv() as $envkey => $value) { + // Replace all settings with their respective environment variable if available + $keyArray = explode('_', $envkey); + if ($keyArray[0] === 'RSSBRIDGE') { + $header = strtolower($keyArray[1]); + $key = strtolower($keyArray[2]); + if ($value === 'true' || $value === 'false') { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + Configuration::$config[$header][$key] = $value; + } + } + + if ( + !is_string(self::getConfig('system', 'timezone')) + || !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)) + ) { + self::reportConfigurationError('system', 'timezone'); + } + + date_default_timezone_set(self::getConfig('system', 'timezone')); + + if (!is_string(self::getConfig('proxy', 'url'))) { + self::reportConfigurationError('proxy', 'url', 'Is not a valid string'); + } + + if (!empty(self::getConfig('proxy', 'url'))) { + /** URL of the proxy server */ + define('PROXY_URL', self::getConfig('proxy', 'url')); + } + + if (!is_bool(self::getConfig('proxy', 'by_bridge'))) { + self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean'); + } + + /** True if proxy usage can be enabled selectively for each bridge */ + define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge')); + + if (!is_string(self::getConfig('proxy', 'name'))) { + self::reportConfigurationError('proxy', 'name', 'Is not a valid string'); + } + + /** Name of the proxy server */ + define('PROXY_NAME', self::getConfig('proxy', 'name')); + + if (!is_string(self::getConfig('cache', 'type'))) { + self::reportConfigurationError('cache', 'type', 'Is not a valid string'); + } + + if (!is_bool(self::getConfig('cache', 'custom_timeout'))) { + self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean'); + } + + /** True if the cache timeout can be specified by the user */ + define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout')); + + if (!is_bool(self::getConfig('authentication', 'enable'))) { + self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean'); + } + + if (!is_string(self::getConfig('authentication', 'username'))) { + self::reportConfigurationError('authentication', 'username', 'Is not a valid string'); + } + + if (!is_string(self::getConfig('authentication', 'password'))) { + self::reportConfigurationError('authentication', 'password', 'Is not a valid string'); + } + + if ( + !empty(self::getConfig('admin', 'email')) + && !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL) + ) { + self::reportConfigurationError('admin', 'email', 'Is not a valid email address'); + } + + if (!is_bool(self::getConfig('admin', 'donations'))) { + self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean'); + } + + if (!is_string(self::getConfig('error', 'output'))) { + self::reportConfigurationError('error', 'output', 'Is not a valid String'); + } + + if ( + !is_numeric(self::getConfig('error', 'report_limit')) + || self::getConfig('error', 'report_limit') < 1 + ) { + self::reportConfigurationError('admin', 'report_limit', 'Value is invalid'); + } + } + + /** + * Returns the value of a parameter identified by section and key. + * + * @param string $section The section name. + * @param string $key The property name (key). + * @return mixed|null The parameter value. + */ + public static function getConfig($section, $key) + { + if (array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) { + return self::$config[$section][$key]; + } + + return null; + } + + /** + * Returns the current version string of RSS-Bridge. + * + * This function returns the contents of {@see Configuration::$VERSION} for + * regular installations and the git branch name and commit id for instances + * running in a git environment. + * + * @return string The version string. + */ + public static function getVersion() + { + $headFile = PATH_ROOT . '.git/HEAD'; + + // '@' is used to mute open_basedir warning + if (@is_readable($headFile)) { + $revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1); + $parts = explode('/', $revisionHashFile); + + if (isset($parts[3])) { + $branchName = $parts[3]; + if (file_exists($revisionHashFile)) { + return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7); + } + } + } + + return Configuration::$VERSION; + } + + /** + * Reports an configuration error for the specified section and key to the + * user and ends execution + * + * @param string $section The section name + * @param string $key The configuration key + * @param string $message An optional message to the user + * + * @return void + */ + private static function reportConfigurationError($section, $key, $message = '') + { + $report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL; + + if (file_exists(FILE_CONFIG)) { + $report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL; + } elseif (!file_exists(FILE_CONFIG_DEFAULT)) { + $report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL; + } else { + $report .= 'The default configuration file is broken.' . PHP_EOL + . 'Restore the original file from ' . REPOSITORY . PHP_EOL; + } + + $report .= $message; + self::reportError($report); + } + + /** + * Reports an error message to the user and ends execution + * + * @param string $message The error message + * + * @return void + */ + private static function reportError($message) + { + header('Content-Type: text/plain', true, 500); + die('Configuration error' . PHP_EOL . $message); + } } |