Commit 723427bf authored by Marc Egger's avatar Marc Egger
Browse files

Config.php refactor to read config before store initialized

parent c97c1b9c
Pipeline #3851 failed with stages
in 2 minutes and 12 seconds
......@@ -13,6 +13,7 @@ require_once(__DIR__ . '/../../vendor/autoload.php');
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Report\Html2Pdf;
use IMATHUZH\Qfq\Core\Store\Config;
/**
......@@ -20,7 +21,8 @@ use IMATHUZH\Qfq\Core\Report\Html2Pdf;
*/
try {
Path::setMainPaths(Path::API_TO_APP);
$html2pdf = new Html2Pdf();
Config::readConfig('');
$html2pdf = new Html2Pdf(Config::getConfigArray());
$html2pdf->outputHtml2Pdf();
......
......@@ -496,8 +496,6 @@ const SYSTEM_DB_NAME_DATA = 'dbNameData';
const SYSTEM_DB_NAME_QFQ = 'dbNameQfq';
const SYSTEM_DB_NAME_T3 = 'dbNameT3';
const SYSTEM_QFQ_LOG_PATH = 'logPath'; // absolute or relative to app
const SYSTEM_QFQ_LOG_PATHFILENAME = 'qfqLog'; // absolute or relative to app
const SYSTEM_MAIL_LOG_PATHFILENAME = 'mailLog'; // absolute or relative to app
......
......@@ -104,6 +104,10 @@ class Path
const EXT_TO_SEND_EMAIL_FILE = '/Classes/External/sendEmail';
/**
* Manually set the paths which are not constant nor can be inferred by other paths.
* This function must be called at the beginning of every entry point, to tell the Path class, where things are
* relative to the CWD.
*
* @param string $cwdToApp
* @throws \CodeException
* @throws \UserFormException
......@@ -121,7 +125,7 @@ class Path
*/
public static function absoluteApp(/* path parts to append */): string
{
return realpath(self::cwdToApp(func_get_args()));
return self::realpath(self::cwdToApp(func_get_args()));
}
/**
......@@ -130,7 +134,7 @@ class Path
*/
public static function absoluteExt(/* path parts to append */): string
{
return realpath(self::cwdToExt(func_get_args()));
return self::realpath(self::cwdToExt(func_get_args()));
}
/**
......@@ -139,7 +143,7 @@ class Path
*/
public static function absoluteLog(/* path parts to append */): string
{
return realpath(self::cwdToLog(func_get_args()));
return self::realpath(self::cwdToLog(func_get_args()));
}
/**
......@@ -274,6 +278,48 @@ class Path
self::$overloadAbsoluteMailLogFile = $newPath;
}
/**
* Override the paths of sql.log, qfq.log, mail.log using the values from the config file or Typo3.
*
* @throws \UserFormException
*/
public static function overrideLogPathsFromConfig()
{
// QFQ log
$absoluteQfqLogFile = Config::get(SYSTEM_QFQ_LOG_PATHFILENAME);
if (!empty($absoluteQfqLogFile)) {
self::setAbsoluteQfqLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteQfqLogFile));
}
// Mail log
$absoluteMailLogFile = Config::get(SYSTEM_MAIL_LOG_PATHFILENAME);
if (!empty($absoluteMailLogFile)) {
self::setAbsoluteMailLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteMailLogFile));
}
// SQL log
$absoluteSqlLogFile = Config::get(SYSTEM_SQL_LOG_PATHFILENAME);
if (!empty($absoluteSqlLogFile)) {
self::setAbsoluteSqlLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteSqlLogFile));
}
}
/**
* Return the second path if it is absolute. Otherwise concatenate and return the two paths.
*
* @param string $path1
* @param string $path2
* @return string
* @throws \UserFormException
*/
public static function joinIfNotAbsolute (string $path1, string $path2): string
{
if ($path2 !== '' && $path2[0] === '/') {
return $path2;
}
return self::join($path1, $path2);
}
/**
* Join the arguments together as a path.
* The second argument may be an array of parts. (further arguments are ignored in that case)
......@@ -343,6 +389,11 @@ class Path
}
/**
* Searches these places for log directory:
* 1) project-directory/log
* 2) fileadmin/protected/log
* If not found create log dir in: project-directory/log
*
* @throws \UserFormException
*/
private static function findAndSetLogPath()
......@@ -416,6 +467,8 @@ EOF;
}
/**
* Throw an exception if the given path is not set (i.e. === null).
*
* @param $path
* @throws \UserFormException
*/
......@@ -426,12 +479,31 @@ EOF;
}
/**
* Throw exception if path does not start with '/'
*
* @param $path
* @throws \UserFormException
*/
private static function enforcePathIsAbsolute($path) {
if($path[0] !== '/') {
private static function enforcePathIsAbsolute(string $path) {
if($path !== '' && $path[0] === '/') {
return;
}
Thrower::userFormException('Path is not absolute', "Path does not start with '/' : $path");
}
/**
* PHP System function: realpath() with QFQ exception
*
* @param string $path
* @return string
* @throws \UserFormException
*/
private static function realpath(string $path): string
{
$result = realpath($path);
if ($result === false) {
Thrower::userFormException('Path not found.', "Either file/dir not exists or access not permitted: $path.");
}
return $result;
}
}
\ No newline at end of file
......@@ -26,6 +26,7 @@ use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Report\Monitor;
use IMATHUZH\Qfq\Core\Report\Report;
use IMATHUZH\Qfq\Core\Report\ReportAsFile;
use IMATHUZH\Qfq\Core\Store\Config;
use IMATHUZH\Qfq\Core\Store\FillStoreForm;
use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Sip;
......@@ -121,6 +122,8 @@ class QuickFormQuery {
*/
public function __construct(array $t3data = array(), $phpUnit = false, $inlineReport = true) {
Config::readConfig();
#TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
$this->phpUnit = $phpUnit;
$this->inlineReport = $inlineReport;
......
......@@ -69,9 +69,6 @@ class Html2Pdf {
public function __construct(array $config = array(), $phpUnit = false) {
#TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
if (count($config) == 0) {
$config = Config::readConfig('');
}
$this->config = $config;
......
......@@ -193,7 +193,7 @@ class Report {
$sqlLog = $this->store->getVar(TYPO3_SQL_LOG_ABSOLUTE, STORE_TYPO3);
if (false !== $sqlLog) {
$sqlLogAbsolute = HelperFile::joinPathFilename(realpath(Path::cwdToApp()), $sqlLog);
$sqlLogAbsolute = HelperFile::joinPathFilename(Path::absoluteApp(), $sqlLog);
Path::setAbsoluteSqlLogFile($sqlLogAbsolute);
}
......
......@@ -21,6 +21,8 @@ use IMATHUZH\Qfq\Core\Helper\OnString;
*/
class Config {
private static $config = null;
const CONFIG_REQUIRED_TEMPLATE = [
SYSTEM_DB_1_USER => null,
SYSTEM_DB_1_PASSWORD => null,
......@@ -28,6 +30,86 @@ class Config {
SYSTEM_DB_1_NAME=> null,
];
/**
* Get config value with given key. Throws exception if config has not been read.
*
* @param string $key
* @return mixed
* @throws \UserFormException
*/
public static function get(string $key)
{
if (self::$config === null) {
Thrower::userFormException("Config error.", "Config was accessed before it was read. Execute Config::readConfig() first.");
}
return self::$config[$key];
}
/**
* Returns a copy of the config array. Throws exception if config has not been read.
*
* @return array
* @throws \UserFormException
*/
public static function getConfigArray(): array
{
if (self::$config === null) {
Thrower::userFormException("Config error.", "Config was accessed before it was read. Execute Config::readConfig() first.");
}
return self::$config;
}
/**
* Read qfq.json (merge with Typo3-qfq config if exists)
* Deprecated config file typo3conf/config.qfq.php is translated to JSON in PATH:cwdToProject(..)
*
* @param string $PhpUnitOverloadCwdToConfigFile
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function readConfig($PhpUnitOverloadCwdToConfigFile = '') {
if (self::$config !== null && $PhpUnitOverloadCwdToConfigFile === '') {
return;
}
// read and parse config. Throw exception if not exists.
$cwdToConfigFile = $PhpUnitOverloadCwdToConfigFile === '' ? Path::cwdToProject(CONFIG_QFQ_JSON) : $PhpUnitOverloadCwdToConfigFile;
if (!file_exists($cwdToConfigFile)) {
HelperFile::file_put_contents(Path::cwdToProject(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT));
Thrower::userFormException("Please create qfq config file '" . CONFIG_QFQ_JSON . "' in project directory. Example config file '" . CONFIG_QFQ_JSON_EXAMPLE . "' was created in project directory.", "Project directory: " . realpath(Path::cwdToProject()));
}
$config = HelperFile::json_decode(HelperFile::file_get_contents($cwdToConfigFile));
// check required keys
foreach (self::CONFIG_REQUIRED_TEMPLATE as $key => $value) {
if (!array_key_exists($key, $config) || is_null($config[$key]) || $config[$key] === '') {
Thrower::userFormException("Required key '$key' missing in config file " . CONFIG_QFQ_JSON, "Config file: $cwdToConfigFile");
}
}
if ($PhpUnitOverloadCwdToConfigFile === '') {
// Settings in qfq.json overwrite T3 settings
$configT3qfq = self::readTypo3QfqConfig();
$config = array_merge($configT3qfq, $config);
}
$config = self::renameConfigElements($config);
$config = self::setDefaults($config);
self::checkDeprecated($config);
self::checkForAttack($config);
// Copy values to detect custom settings later
$config[F_FE_DATA_PATTERN_ERROR_SYSTEM] = $config[F_FE_DATA_PATTERN_ERROR];
self::$config = $config;
// Set log paths
Path::overrideLogPathsFromConfig();
}
/**
* Iterates over all 30 custom vars, explode them to split between key and value, append to $config.
*
......@@ -58,7 +140,7 @@ class Config {
}
/**
* Overwrite the config file with data from given array.
* Overwrite the qfq config file with data from given array.
*
* @param array $config
* @throws \UserFormException
......@@ -91,51 +173,6 @@ class Config {
HelperFile::unlink($cwdToOldConfigFile);
}
/**
* Read qfq.json (merge with Typo3-qfq config if exists)
* Deprecated config file typo3conf/config.qfq.php is translated to JSON in PATH:cwdToProject(..)
*
* @param string $PhpUnitOverloadCwdToConfigFile
* @return array
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function readConfig($PhpUnitOverloadCwdToConfigFile = '') {
// read and parse config
$cwdToConfigFile = $PhpUnitOverloadCwdToConfigFile === '' ? Path::cwdToProject(CONFIG_QFQ_JSON) : $PhpUnitOverloadCwdToConfigFile;
if (!file_exists($cwdToConfigFile)) {
HelperFile::file_put_contents(Path::cwdToProject(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT));
Thrower::userFormException("Please create qfq config file '" . CONFIG_QFQ_JSON . "' in project directory. Example config file '" . CONFIG_QFQ_JSON_EXAMPLE . "' was created in project directory.", "Project directory: " . realpath(Path::cwdToProject()));
}
$config = HelperFile::json_decode(HelperFile::file_get_contents($cwdToConfigFile));
// check required keys
foreach (self::CONFIG_REQUIRED_TEMPLATE as $key => $value) {
if (!array_key_exists($key, $config) || is_null($config[$key]) || $config[$key] === '') {
Thrower::userFormException("Required key '$key' missing in config file " . CONFIG_QFQ_JSON, "Config file: $cwdToConfigFile");
}
}
if ($PhpUnitOverloadCwdToConfigFile === '') {
// Settings in qfq.json overwrite T3 settings
$configT3qfq = self::readTypo3QfqConfig();
$config = array_merge($configT3qfq, $config);
}
$config = self::renameConfigElements($config);
$config = self::setDefaults($config);
self::checkDeprecated($config);
self::checkForAttack($config);
// Copy values to detect custom settings later
$config[F_FE_DATA_PATTERN_ERROR_SYSTEM] = $config[F_FE_DATA_PATTERN_ERROR];
return $config;
}
/**
* Read Typo3-QFQ config first from global variable, then from typo3conf/Localconfig.php.
* If both not exist, return empty array.
......@@ -271,7 +308,8 @@ class Config {
public static function attackDetectedExitNow(array $config = array(), $reason = '') {
if (count($config) == 0) {
$config = self::readConfig();
self::readConfig();
$config = self::getConfigArray();
}
Logger::logMessage(Logger::linePre() . 'Security: attack detected' . PHP_EOL . $reason, Path::absoluteQfqLogFile());
......@@ -322,7 +360,6 @@ class Config {
SYSTEM_RENDER => SYSTEM_RENDER_SINGLE,
SYSTEM_DATE_FORMAT => 'yyyy-mm-dd',
SYSTEM_SHOW_DEBUG_INFO => SYSTEM_SHOW_DEBUG_INFO_AUTO,
SYSTEM_QFQ_LOG_PATH => '',
SYSTEM_MAIL_LOG_PATHFILENAME => '',
SYSTEM_QFQ_LOG_PATHFILENAME => '',
SYSTEM_SQL_LOG_PATHFILENAME => '',
......@@ -375,7 +412,7 @@ class Config {
SYSTEM_DB_UPDATE => SYSTEM_DB_UPDATE_AUTO,
SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS => SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS_DEFAULT,
SYSTEM_SESSION_TIMEOUT_SECONDS => self::getPhpSessionTimeout(),
SYSTEM_SESSION_TIMEOUT_SECONDS => Session::getPhpSessionTimeout(),
SYSTEM_DOCUMENTATION_QFQ => SYSTEM_DOCUMENTATION_QFQ_URL,
SYSTEM_ENTER_AS_SUBMIT => 1,
......@@ -464,45 +501,4 @@ class Config {
return $config;
}
/**
* Check Session Timeout
*
* @param $timeout
* @throws \UserFormException
*/
public static function checkSessionTimeout($timeout) {
if (self::getPhpSessionTimeout() < $timeout) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the PHP config 'session.gc_maxlifetime' ("
. ini_get('session.gc_maxlifetime') . ") and/or 'session.cookie_lifetime' ("
. ini_get('session.cookie_lifetime') . ")");
}
if ($timeout > SYSTEM_COOKIE_LIFETIME) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the hardcoded cookie lifetime (" . SYSTEM_COOKIE_LIFETIME . ")");
}
}
/**
* Get the minimum of 'session.cookie_lifetime' and 'session.gc_maxlifetime'
* A zero means unlimited and will be limited to one day.
*
* @return int|string
*/
private static function getPhpSessionTimeout() {
$timeout = ini_get('session.cookie_lifetime');
$gc_maxlifetime = ini_get('session.gc_maxlifetime');
if ($timeout == 0) {
$timeout = 86400;
}
if ($gc_maxlifetime == 0) {
// Just to set a limit
$timeout = 86400;
}
return $timeout > $gc_maxlifetime ? $gc_maxlifetime : $timeout;
}
}
\ No newline at end of file
......@@ -314,7 +314,7 @@ class Session
public static function checkSessionExpired($timeout) {
// Just to be sure that the given $timeout is supported by the current php.ini setup
config::checkSessionTimeout($timeout);
self::checkSessionTimeout($timeout);
if (self::$lastActivity === false || $timeout === false || $timeout == 0) {
return;
......@@ -327,6 +327,47 @@ class Session
}
}
/**
* Check Session Timeout
*
* @param $timeout
* @throws \UserFormException
*/
public static function checkSessionTimeout($timeout) {
if (self::getPhpSessionTimeout() < $timeout) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the PHP config 'session.gc_maxlifetime' ("
. ini_get('session.gc_maxlifetime') . ") and/or 'session.cookie_lifetime' ("
. ini_get('session.cookie_lifetime') . ")");
}
if ($timeout > SYSTEM_COOKIE_LIFETIME) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the hardcoded cookie lifetime (" . SYSTEM_COOKIE_LIFETIME . ")");
}
}
/**
* Get the minimum of 'session.cookie_lifetime' and 'session.gc_maxlifetime'
* A zero means unlimited and will be limited to one day.
*
* @return int|string
*/
public static function getPhpSessionTimeout() {
$timeout = ini_get('session.cookie_lifetime');
$gc_maxlifetime = ini_get('session.gc_maxlifetime');
if ($timeout == 0) {
$timeout = 86400;
}
if ($gc_maxlifetime == 0) {
// Just to set a limit
$timeout = 86400;
}
return $timeout > $gc_maxlifetime ? $gc_maxlifetime : $timeout;
}
/**
* Returns $flagFeUserChanged. In case it's true, set it to false.
*
......
......@@ -303,7 +303,7 @@ class Store {
private static function checkMandatoryParameter(array $config) {
// Check mandatory config vars.
$names = array_merge([SYSTEM_SQL_LOG_PATHFILENAME, SYSTEM_SQL_LOG_MODE],
$names = array_merge([SYSTEM_SQL_LOG_MODE],
self::dbCredentialName($config[SYSTEM_DB_INDEX_DATA]),
self::dbCredentialName($config[SYSTEM_DB_INDEX_QFQ]));
......@@ -415,7 +415,8 @@ class Store {
*/
private static function fillStoreSystem($PhpUnitOverloadCwdToConfigFile = '') {
$config = Config::readConfig($PhpUnitOverloadCwdToConfigFile);
Config::readConfig($PhpUnitOverloadCwdToConfigFile);
$config = Config::getConfigArray();
$config = self::adjustConfig($config);
$config = self::setAutoConfigValue($config);
......
#!/bin/bash
# run a single test function. e.g. "test_basic_functionality.TestBasicFunctionality.test_qfq_form_switch_pill"
TEST_FUNCTION="test_basic_functionality.TestBasicFunctionality.test_qfq_valid_file_upload" # NOTE: make sure there are no trailing spaces!
TEST_FUNCTION="" # NOTE: make sure there are no trailing spaces!
export SELENIUM_URL="https://webwork16.math.uzh.ch/megger/qfq/typo3conf/ext/qfq/NoT3Page"
export SELENIUM_BROWSER="chrome"
export SELENIUM_DRIVER_PATH="${PWD}/chromedriver"
export SELENIUM_HEADLESS="no"
export SELENIUM_SLOWDOWN=0
export SELENIUM_SLOWDOWN=1
# download chromedriver (Chrome)
if [ ! -f "chromedriver" ]; then
......
......@@ -83,9 +83,6 @@ sqlLogMode = modify
# cat=debug/sql; type=string; label=SQL log mode for AutoCron:Default is 'error'. Modes see 'sqlLogMode'.
sqlLogModeAutoCron = error
# cat=debug/sql; type=string; label=log directory:Default is '<qfq project path>/log'. Contains all log files. Path is absolute or relative to '<site path>'.
logPath =
# cat=debug/sql; type=string; label=SQL log file:By default in log directory. A logfile of fired SQL statements. PathFile is absolute or relative to '<site path>'.
sqlLog =
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment