Commit 426aa27e authored by Marc Egger's avatar Marc Egger
Browse files

Refs #11035 Replace config.qfq.php with qfq.json. find project path by searching for config.

parent e658bbf8
Pipeline #3720 failed with stages
in 2 minutes and 9 seconds
......@@ -7,8 +7,9 @@
*/
const EXT_KEY = 'qfq';
const CONFIG_QFQ_INI = "config.qfq.ini"; // QFQ configuration file: db access - deprecated
const CONFIG_QFQ_PHP = "config.qfq.php"; // QFQ configuration file: db access
const CONFIG_QFQ_INI = "config.qfq.ini"; // QFQ configuration file: db access - deprecated
const CONFIG_QFQ_PHP = "config.qfq.php"; // QFQ configuration file: db access - deprecated
const CONFIG_QFQ_JSON = "qfq.json"; // QFQ configuration file: db access + what used to be in LocalConfig.php
const CONFIG_T3 = 'LocalConfiguration.php'; // T3 config file
const QFQ_TEMP_FILE_PATTERN = 'qfq.split.XXXXX';
......
......@@ -240,13 +240,7 @@ class FormAsFile
$form[F_FILE_FORM_ELEMENT] = $formElements;
$formJson = json_encode($form, JSON_PRETTY_PRINT);
$pathFileName = self::formPathFileName($formName, $database);
$success = file_put_contents($pathFileName, $formJson);
if ($success === false) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Writing form file failed: " . baseName($pathFileName),
ERROR_MESSAGE_TO_DEVELOPER => "Can't write to file '$pathFileName'"]),
ERROR_IO_WRITE_FILE);
}
HelperFile::file_put_contents($pathFileName, $formJson);
// Update column fileStats
$fileStats = self::formFileStatsJson($pathFileName);
......@@ -335,21 +329,7 @@ class FormAsFile
public static function enforceFormFileWritable(string $formName, Database $database) // : void
{
$pathFileName = self::formPathFileName($formName, $database);
if (file_exists($pathFileName)) {
if (!is_writable($pathFileName)) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Can't write to file: " . baseName($pathFileName),
ERROR_MESSAGE_TO_DEVELOPER => "Can't write to file '$pathFileName'. Check permissions."]),
ERROR_IO_WRITE_FILE);
}
} else {
if (!is_writable(dirname($pathFileName))) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Can't write to form directory. Check permissions.",
ERROR_MESSAGE_TO_DEVELOPER => "Can't write to directory: " . dirname($pathFileName)]),
ERROR_IO_WRITE_FILE);
}
}
HelperFile::enforce_writable_or_creatable($pathFileName);
}
......@@ -606,27 +586,25 @@ class FormAsFile
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private static function formPath(Database $database): string
{
$qfqProjectDirRelToCwd = Path::cwdToApp(Store::getInstance()->getVar(SYSTEM_QFQ_PROJECT_DIR_SECURE_REL_TO_APP, STORE_SYSTEM));
$formPath = Path::join($qfqProjectDirRelToCwd, Path::PROJECT_DIR_TO_FORM);
if (!is_dir($formPath)) {
$cwdToForm = Path::cwdToProject(Path::PROJECT_TO_FORM);
if (!is_dir($cwdToForm)) {
// create path
$success = mkdir($formPath, 0777, true);
$success = mkdir($cwdToForm, 0777, true);
if ($success === false) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Can't create form file path.",
ERROR_MESSAGE_TO_DEVELOPER => "Can't create path: " . $formPath]),
ERROR_MESSAGE_TO_DEVELOPER => "Can't create path: " . $cwdToForm]),
ERROR_IO_WRITE_FILE);
}
// export all forms
self::exportAllForms($database);
}
return $formPath;
return $cwdToForm;
}
/**
......
......@@ -579,5 +579,36 @@ class HelperFile {
ERROR_IO_WRITE_FILE);
}
}
/**
* Wrapper for is_writeable which throws exception on failure.
* Throws exception if file does not exist!
*
* @param $path
* @throws \UserFormException
*/
public static function enforce_writable($path)
{
if (!is_writeable($path)) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => 'File/directory not found or not writable.',
ERROR_MESSAGE_TO_DEVELOPER => "Can't write to file/directory '$path'"]), ERROR_IO_WRITE_FILE);
}
}
/**
* Like enforce_writable but does not throw exception if file does not exist and the containing directory is writable.
*
* @param $pathFileName
* @throws \UserFormException
*/
public static function enforce_writable_or_creatable($pathFileName)
{
if (file_exists($pathFileName)) {
self::enforce_writable($pathFileName);
} else {
self::enforce_writable(dirname($pathFileName));
}
}
}
......@@ -3,8 +3,21 @@
namespace IMATHUZH\Qfq\Core\Helper;
/*
* Naming convention of path variables:
*
* 1) name a path by its origin and its destination separated by 'to'. E.g. APP_TO_SYSTEM_LOG, $appToProject.
* 2) if the destination is a file, append "File". E.g. APP_TO_SYSTEM_QFQ_LOG_FILE.
* 3) if a path has to be variable, create a setter and getter. E.g. self::setCwdToApp(), self::cwdToApp(), private static $cwdToApp.
* 4) a path getter appends the given arguments to the requested path using self::join(..., func_get_args()). E.g. see cwdToApp().
* 5) additional path getters may be defined which combine other getters. E.g. see cwdToApi().
* 6) avoid defining redundant paths in constants. E.g. create cwdToApi() by combining cwdToExt() and extToApi() instead of defining CWD_TO_API.
*/
/*
Unittests:
Path::apiRelToCwd(API_SAVE_PHP) === API_DIR . '/save.php'
Path::apiRelToCwd(API_LOAD_PHP) === API_DIR . '/load.php'
Path::apiRelToCwd(API_FILE_PHP) . '?' . $actionUpload === API_DIR . '/file.php?' . $actionUpload
......@@ -35,9 +48,11 @@ Path::join($qfqProjectDirRelToCwd, Path::REPORT_REL_TO_PROJECT_DIR) === "../../.
*/
use IMATHUZH\Qfq\Core\Store\Config;
class Path
{
// Path from CWD.
// App
// This should be manually overwritten (using Path::setCwdToApp()) if the CWD is not the one containing the typo3 index.php.
private static $cwdToApp = '';
......@@ -45,15 +60,21 @@ class Path
const APP_TO_EXT = 'typo3conf/ext/qfq';
// QFQ Project dir
const PROJECT_DIR_TO_FORM = 'form';
private static $appToProject = null;
const APP_TO_PROJECT_NEW = '../';
const APP_TO_PROJECT_OLD = 'fileadmin/protected/qfqProject';
const PROJECT_TO_FORM = 'form';
const PROJECT_DIR_TO_REPORT = 'report';
// Config
const APP_TO_TYPO3_CONFIG = 'typo3conf';
// API
const EXT_TO_API = 'Classes/Api';
const API_TO_APP = '../../../../../';
// Icons
const EXT_TO_GFX_INFO = 'Resources/Public/icons/note.gif';
const EXT_TO_GFX_INFO_FILE = 'Resources/Public/icons/note.gif';
const EXT_TO_PATH_ICONS = 'Resources/Public/icons';
// Log files
......@@ -73,57 +94,151 @@ class Path
// Twig
const EXT_TO_TWIG_TEMPLATES = 'Resources/Public/twig_templates';
/**
* @param string $newPath
*/
public static function setCwdToApp(string $newPath)
{
self::$cwdToApp = $newPath;
}
public static function cwdToExt(string $pathRelToExt = ''): string
/**
* @return string
* @throws \UserFormException
*/
public static function cwdToApp(/* path parts to append */): string
{
return self::join(self::cwdToApp(self::APP_TO_EXT), $pathRelToExt);
return self::join(self::$cwdToApp, func_get_args());
}
public static function extToApi(string $pathRelToApi = ''): string
/**
* @param string $newPath
*/
public static function setAppToProject(string $newPath)
{
return self::join(self::EXT_TO_API, $pathRelToApi);
self::$appToProject = $newPath;
}
public static function cwdToApi(string $pathRelToApi = ''): string
/**
* @return string
* @throws \CodeException
* @throws \UserFormException
*/
public static function appToProject(/* path parts to append */): string
{
return self::cwdToExt(self::extToApi($pathRelToApi));
if (is_null(self::$appToProject)) {
self::findAndSetProjectPath();
}
return self::join(self::$appToProject, func_get_args());
}
public static function cwdToApp(string $pathRelToExt = ''): string
/**
* @return string
* @throws \UserFormException
*/
public static function cwdToProject(/* path parts to append */): string
{
return self::join(self::$cwdToApp, $pathRelToExt);
return self::cwdToApp(self::appToProject(func_get_args()));
}
public static function join(/* path parts */): string
/**
* @return string
* @throws \UserFormException
*/
public static function cwdToExt(/* path parts to append */): string
{
// Filter out empty string and null arguments
$parts = array_filter(func_get_args(), function($value) { return !is_null($value) && $value !== ''; });
return self::cwdToApp(self::APP_TO_EXT, func_get_args());
}
if (empty($parts)) {
return '';
}
$firstPart = array_shift($parts);
if (empty($parts)) {
return $firstPart;
/**
* @return string
* @throws \UserFormException
*/
public static function extToApi(/* path parts to append */): string
{
return self::join(self::EXT_TO_API, func_get_args());
}
/**
* @return string
* @throws \UserFormException
*/
public static function cwdToApi(/* path parts to append */): string
{
return self::cwdToExt(self::extToApi(func_get_args()));
}
/**
* Find the project directory by searching for config.qfq.json in the following locations
* 1) above app (app/../)
* 2) in fileadmin (app/fileadmin/protected/qfqProject/)
* 3) in typo3conf (app/typo3conf/) => moves config to fileadmin (see 2)
* 4) not found => set path to app/../ (see 1)
*
* @throws \CodeException
* @throws \UserFormException
*/
public static function findAndSetProjectPath()
{
if (is_readable(Path::cwdToApp(Path::APP_TO_PROJECT_NEW, CONFIG_QFQ_JSON))) {
Path::setAppToProject(Path::APP_TO_PROJECT_NEW);
} elseif (is_readable(Path::cwdToApp(Path::APP_TO_PROJECT_OLD, CONFIG_QFQ_JSON))) {
Path::setAppToProject(Path::APP_TO_PROJECT_OLD);
} elseif (is_readable(Path::cwdToApp(Path::APP_TO_TYPO3_CONFIG, CONFIG_QFQ_PHP))) {
Path::setAppToProject(Path::APP_TO_PROJECT_OLD);
// read old config.qfq.php
$cwdToOldConfigFile = Path::cwdToApp(Path::APP_TO_TYPO3_CONFIG, CONFIG_QFQ_PHP);
HelperFile::enforce_writable($cwdToOldConfigFile); // so we can delete it.
$config = include($cwdToOldConfigFile);
// write new qfq.config.json
Config::write_config($config);
// remove old
HelperFile::unlink($cwdToOldConfigFile);
} else {
Path::setAppToProject(Path::APP_TO_PROJECT_NEW);
}
}
/**
* Join the arguments together as a path.
* The second argument may be an array of parts. (further arguments are ignored in that case)
*
* @return string
* @throws \UserFormException
*/
public static function join(/* path parts to append */): string
{
$parts = func_get_args();
// concatenate all parts (arrays are flattened)
$path = '';
array_walk_recursive($parts, function ($part) use (&$path) {
// Trailing path parts may not be absolute
foreach ($parts as $part) {
if ($part[0] === '/') {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => 'Failed: Join path.',
ERROR_MESSAGE_TO_DEVELOPER => "Trailing path parts may not start with '/'. Trailing part: '$part'."]),
ERROR_PATH_INVALID);
// Filter out empty string and null arguments
if (is_null($part) || $part === '') {
return;
}
}
$path = $firstPart . '/' . implode($parts,'/');
if ($path === '') {
$path .= $part;
} else {
// Trailing path parts may not be absolute
if ($part[0] === '/') {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => 'Failed: Join path.',
ERROR_MESSAGE_TO_DEVELOPER => "Trailing path parts may not start with '/'. Trailing part: '$part'."]),
ERROR_PATH_INVALID);
}
$path .= '/' . $part;
}
});
// remove multiple occurances of '/'
// remove multiple occurrences of '/'
return preg_replace('/\/{2,}/','/', $path);
}
}
\ No newline at end of file
......@@ -236,7 +236,7 @@ class Support {
*/
public static function doTooltip($htmlId, $tooltipText) {
return "<img " . self::doAttribute('id', $htmlId) . " src='" . Path::cwdToExt(Path::EXT_TO_GFX_INFO) . "' title=\"" . htmlentities($tooltipText) . "\">";
return "<img " . self::doAttribute('id', $htmlId) . " src='" . Path::cwdToExt(Path::EXT_TO_GFX_INFO_FILE) . "' title=\"" . htmlentities($tooltipText) . "\">";
}
/**
......
......@@ -167,15 +167,12 @@ class ReportAsFile
* Create path if it doesn't exist.
*
* @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
private static function reportPath(): string
{
$qfqProjectDirRelToCwd = Path::cwdToApp(Store::getInstance()->getVar(SYSTEM_QFQ_PROJECT_DIR_SECURE_REL_TO_APP, STORE_SYSTEM));
$reportPath = Path::join($qfqProjectDirRelToCwd, Path::PROJECT_DIR_TO_REPORT);
HelperFile::createPathRecursive($reportPath);
return $reportPath;
$cwdToReport = Path::cwdToProject(Path::PROJECT_DIR_TO_REPORT);
HelperFile::createPathRecursive($cwdToReport);
return $cwdToReport;
}
}
\ No newline at end of file
......@@ -8,11 +8,12 @@
namespace IMATHUZH\Qfq\Core\Store;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Helper\OnString;
/**
* Class Config
* @package qfq
......@@ -75,29 +76,59 @@ class Config {
return $config;
}
/**
* Overwrite the config file with data from given array.
*
* @param array $config
* @throws \UserFormException
*/
public static function write_config(array $config)
{
$cwdToProject = Path::cwdToProject();
HelperFile::createPathRecursive($cwdToProject);
HelperFile::file_put_contents(Path::join($cwdToProject, CONFIG_QFQ_JSON), json_encode($config, JSON_PRETTY_PRINT));
}
/**
* Read config.qfq.ini. In case
*
* @param string $fileConfigPhp
* @param string $fileConfigPhpRelToCwd
* @return array
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function readConfig($fileConfigPhp = '') {
public static function readConfig($fileConfigPhpRelToCwd = '') {
/////// DEBUG /////////
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => json_encode([Path::appToProject(), Path::cwdToProject('hello.php')], JSON_PRETTY_PRINT),
ERROR_MESSAGE_TO_DEVELOPER => json_encode([], JSON_PRETTY_PRINT)
]));
///////////////////////
// TODO: create config if not exists.
// TODO: read config.
$configT3qfq = array();
$configIni = ''; // outdated config file format
$configIniRelToCwd = ''; // outdated config file format
// // DONT COMMIT: NOT GOOD ENOUGH!
// if (empty($fileConfigPhpRelToCwd) && !isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][EXT_KEY])) {
// $configPathRelToCwd = Path::cwdToApp(Path::APP_TO_CONFIG_DIR);
// $configIniRelToCwd = Path::join($configPathRelToCwd, CONFIG_QFQ_INI);
// $fileConfigPhpRelToCwd = Path::join($configPathRelToCwd, CONFIG_QFQ_PHP);
// }
// Production Path to CONFIG_INI
$pathTypo3Conf = __DIR__ . '/../../../../..';
if (!file_exists($pathTypo3Conf . '/' . CONFIG_T3)) {
$configPathRelToCwd = __DIR__ . '/../../../../..';
if (!file_exists($configPathRelToCwd . '/' . CONFIG_T3)) {
// PHPUnit Path to CONFIG_INI
$pathTypo3Conf = __DIR__ . '/../../..';
$configPathRelToCwd = __DIR__ . '/../../..';
}
// In case of missing $configPhp
if (empty($fileConfigPhp)) {
if (empty($fileConfigPhpRelToCwd)) {
# Read 'LocalConfiguration.php'
if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][EXT_KEY])) {
......@@ -105,34 +136,34 @@ class Config {
$configT3qfq[SYSTEM_DB_NAME_T3] = self::getDbName($GLOBALS['TYPO3_CONF_VARS']['DB']);
} else {
$all = include($pathTypo3Conf . '/' . CONFIG_T3);
$all = include($configPathRelToCwd . '/' . CONFIG_T3);
if (empty($all) || $all === true) {
throw new \UserFormException ("Error read file: " . $pathTypo3Conf . '/' . CONFIG_T3, ERROR_IO_READ_FILE);
throw new \UserFormException ("Error read file: " . $configPathRelToCwd . '/' . CONFIG_T3, ERROR_IO_READ_FILE);
}
$configT3qfq = unserialize($all['EXT']['extConf'][EXT_KEY]);
if (!is_array($configT3qfq)) {
throw new \UserFormException ("Error read file: " . $pathTypo3Conf . '/' . CONFIG_T3, ERROR_IO_READ_FILE);
throw new \UserFormException ("Error read file: " . $configPathRelToCwd . '/' . CONFIG_T3, ERROR_IO_READ_FILE);
}
$configT3qfq[SYSTEM_DB_NAME_T3] = self::getDbName($all['DB']);
unset($all);
}
$configIni = $pathTypo3Conf . '/' . CONFIG_QFQ_INI;
$fileConfigPhp = $pathTypo3Conf . '/' . CONFIG_QFQ_PHP;
$configIniRelToCwd = $configPathRelToCwd . '/' . CONFIG_QFQ_INI;
$fileConfigPhpRelToCwd = $configPathRelToCwd . '/' . CONFIG_QFQ_PHP;
}
// Migrate legacy config file.
if (is_readable($configIni) && !is_readable($fileConfigPhp)) {
self::migrateConfigIniToPhp($configIni, $fileConfigPhp);
if (is_readable($configIniRelToCwd) && !is_readable($fileConfigPhpRelToCwd)) {
self::migrateConfigIniToPhp($configIniRelToCwd, $fileConfigPhpRelToCwd);
}
$config = include($fileConfigPhp);
$config = include($fileConfigPhpRelToCwd);
if ($config === false) {
throw new \UserFormException ("Error read file: " . $fileConfigPhp, ERROR_IO_READ_FILE);
throw new \UserFormException ("Error read file: " . $fileConfigPhpRelToCwd, ERROR_IO_READ_FILE);
}
// in case $configIni doesn't exist: just skip
......
......@@ -113,7 +113,6 @@ $extConf = [
"maxFileSize" => "",
"baseUrl" => "",
"dateFormat" => "dd.mm.yyyy",
"qfqProjectDir" => "fileadmin/protected/qfqProject",
"thumbnailDirSecure" => "fileadmin/protected/qfqThumbnail",
"thumbnailDirPublic" => "typo3temp/qfqThumbnail",
"cmdInkscape" => "inkscape",
......
......@@ -13,9 +13,6 @@ baseUrl =
# cat=config/date; type=string; label=Date format:Default is 'dd.mm.yyyy'. Possible options: 'yyyy-mm-dd', 'dd.mm.yyyy'
dateFormat = dd.mm.yyyy
# cat=config/config; type=string; label=QFQ project directory:Default is 'fileadmin/protected/qfqProject'. Important: secure the directory (recursive) against direct access. Forms and reports are saved in this directory.
qfqProjectDir = fileadmin/protected/qfqProject
# cat=config/config; type=string; label=Thumbnail directory 'secure':Default is 'fileadmin/protected/qfqThumbnail'. Important: secure the directory (recursive) against direct access. Will be used by a special columnname '_thumbnail'.
thumbnailDirSecure = fileadmin/protected/qfqThumbnail
......
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