Commit 39e742bb authored by Carsten  Rose's avatar Carsten Rose
Browse files

Implements #12022 New Escape class HtmlSpecialChar 'h'

parent 706c4ef5
Pipeline #5023 passed with stages
in 5 minutes and 54 seconds
......@@ -756,6 +756,7 @@ const TOKEN_ESCAPE_PASSWORD_T3FE = 'p';
const TOKEN_ESCAPE_NONE = '-';
const TOKEN_ESCAPE_WIPE = 'w';
const TOKEN_ESCAPE_TIMEZONE = 't';
const TOKEN_ESCAPE_HTML_SPECIAL_CHAR = 'h';
const TOKEN_ESCAPE_STOP_REPLACE = 'S';
const TOKEN_ESCAPE_EXCEPTION = 'X';
......
......@@ -465,6 +465,9 @@ class Evaluate {
case TOKEN_ESCAPE_TIMEZONE:
$value = $this->getEuropeanTimezone($value);
break;
case TOKEN_ESCAPE_HTML_SPECIAL_CHAR:
$value = Support::htmlEntityEncodeDecode(MODE_ENCODE, $value);
break;
default:
throw new \UserFormException("Unknown escape qualifier: $escape", ERROR_UNKNOW_SANITIZE_CLASS);
break;
......
......@@ -8,16 +8,16 @@
namespace IMATHUZH\Qfq\Core\Exception;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\QuickFormQuery;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Store\T3Info;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\Support;
/**
......@@ -29,11 +29,11 @@ use IMATHUZH\Qfq\Core\Helper\Support;
*
* Throw with message for User and message for Support.
*
throw new \UserFormException( json_encode(
[ERROR_MESSAGE_TO_USER => 'Failed: chmod',
ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
ERROR_IO_CHMOD);
* throw new \UserFormException( json_encode(
* [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
* ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
* ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
* ERROR_IO_CHMOD);
*
* @package qfq
*/
......@@ -174,7 +174,7 @@ class AbstractException extends \Exception {
$arrMerged[ERROR_MESSAGE_TO_DEVELOPER] = $arrMerged[ERROR_MESSAGE_TO_DEVELOPER] ?? '';
try {
$arrMerged[ERROR_MESSAGE_TO_DEVELOPER] = QuickFormQuery::buildInlineReport(\UserReportException::$report_uid,
\UserReportException::$report_pathFileName, \UserReportException::$report_bodytext,
\UserReportException::$report_pathFileName, \UserReportException::$report_bodytext,
\UserReportException::$report_header) . $arrMerged[ERROR_MESSAGE_TO_DEVELOPER];
} catch (\Error | \Exception $e) {
$arrMerged[ERROR_MESSAGE_TO_DEVELOPER] .= "<br>(inline report editor not available)";
......
......@@ -11,10 +11,12 @@ use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\SqlQuery;
use IMATHUZH\Qfq\Core\Store\Store;
class FormAsFile
{
/**
* Class FormAsFile
* @package IMATHUZH\Qfq\Core\Form
*/
class FormAsFile {
/**
* Remove the form from the DB and insert it using the form file. (only if the form file was changed)
* If the form file can't be read, then the form is deleted from the DB and an exception is thrown.
......@@ -33,22 +35,32 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
public static function importForm(string $formName, Database $database, bool $keepIfNeverExported = true): bool
{
public static function importForm(string $formName, Database $database, bool $keepIfNeverExported = true): bool {
// Get file stats from database form
$formFromDb = $database->selectFormByName($formName, [F_ID, F_FILE_STATS]);
// Get file stats from file system
$absoluteFormFilePath = self::formPathFileName($formName, $database);
$fileReadException = function () use ($absoluteFormFilePath, $database, $formName) {
// Search for form name: convert all to lowercase and compare.
$addHint = implode(', ', array_filter(self::formFileNames($database),
function ($f) use ($formName) {
return strtolower($f) === strtolower($formName);
}));
if ($addHint != '') {
$addHint = 'Similar forms: ' . $addHint;
}
Thrower::userFormException(
"Form file not found or missing permission: '" . baseName($absoluteFormFilePath) . "'. Form names are case sensitive. Similar forms: "
. implode(', ', array_filter(self::formFileNames($database), function ($f) use ($formName) {return strtolower($f) === strtolower($formName);})),
"Form file not found or missing permission: '" . baseName($absoluteFormFilePath) . "'. " . $addHint,
"Form definition file not found or no permission to read file: '$absoluteFormFilePath'"
);
};
if(!file_exists($absoluteFormFilePath)) {
if ($keepIfNeverExported && isset($formFromDb[F_ID]) && (!isset($formFromDb[F_FILE_STATS]) || !self::isValidFileStats($formFromDb[F_FILE_STATS]))) {
if (!file_exists($absoluteFormFilePath)) {
if ($keepIfNeverExported && isset($formFromDb[F_ID]) && (!isset($formFromDb[F_FILE_STATS]) ||
!self::isValidFileStats($formFromDb[F_FILE_STATS]))) {
// if file not exists and form was never exported, then export
self::exportForm($formName, $database);
} else {
......@@ -85,14 +97,17 @@ class FormAsFile
foreach ($formFromFile[F_FILE_FORM_ELEMENT] as $formElementFromFile) {
$keysNotSet = OnArray::keysNotSet([FE_CLASS, FE_NAME], $formElementFromFile);
if (!empty($keysNotSet)) {
Thrower::userFormException('Failed to import form file.', "One or more required keys are missing in FormElement definition in file: '$absoluteFormFilePath'. Missing keys: " . implode(', ', $keysNotSet));
Thrower::userFormException('Failed to import form file.',
"One or more required keys are missing in FormElement definition in file: '$absoluteFormFilePath'. Missing keys: " . implode(', ', $keysNotSet));
}
if ($formElementFromFile[FE_CLASS] === FE_CLASS_CONTAINER) {
if (in_array($formElementFromFile[FE_NAME], $containerNames)) {
Thrower::userFormException('Failed to import form file.', "Multiple formElements of class container with the same name '" . $formElementFromFile[FE_NAME] . "' in form file '$absoluteFormFilePath'");
Thrower::userFormException('Failed to import form file.',
"Multiple formElements of class container with the same name '" . $formElementFromFile[FE_NAME] . "' in form file '$absoluteFormFilePath'");
}
if ($formElementFromFile[FE_NAME] == '') {
Thrower::userFormException('Failed to import form file.', "Found formElement of class container with empty name in form file '$absoluteFormFilePath'");
Thrower::userFormException('Failed to import form file.',
"Found formElement of class container with empty name in form file '$absoluteFormFilePath'");
}
$containerNames[] = $formElementFromFile[FE_NAME];
}
......@@ -122,7 +137,7 @@ class FormAsFile
$feId = self::insertFormElement($formElementFromFile, $formIdNew, $database);
$formElementFromFile[FE_ID] = $feId;
if ($formElementFromFile[FE_CLASS] === FE_CLASS_CONTAINER) {
$containerIds[$formElementFromFile[FE_NAME]] = $feId;
$containerIds[$formElementFromFile[FE_NAME]] = $feId;
}
}
......@@ -131,7 +146,8 @@ class FormAsFile
if (array_key_exists(FE_FILE_CONTAINER_NAME, $formElementFromFile)) {
$containerName = $formElementFromFile[FE_FILE_CONTAINER_NAME];
if (!isset($containerIds[$containerName])) {
Thrower::userFormException('Failed to import form file.', "Key '" . FE_FILE_CONTAINER_NAME . "' points to non-existing container with name '$containerName' in definition of formElement with name '" . $formElementFromFile[FE_NAME] . "' in file '$absoluteFormFilePath'");
Thrower::userFormException('Failed to import form file.',
"Key '" . FE_FILE_CONTAINER_NAME . "' points to non-existing container with name '$containerName' in definition of formElement with name '" . $formElementFromFile[FE_NAME] . "' in file '$absoluteFormFilePath'");
}
$containerId = $containerIds[$containerName];
$feId = $formElementFromFile[FE_ID];
......@@ -186,7 +202,7 @@ class FormAsFile
Logger::logMessage(date('Y.m.d H:i:s ') . ": Importing form file '$pathFileName'. Reason: Empty or non-unique names of container formElements where adjusted during form export.", Path::absoluteQfqLogFile());
self::importForm($formName, $database);
// otherwise => Update column fileStats
// otherwise => Update column fileStats
} else {
$fileStats = self::formFileStatsJson($pathFileName);
list($sql, $parameterArray) = SqlQuery::updateRecord(TABLE_NAME_FORM, [F_FILE_STATS => $fileStats, F_MODIFIED => $form[F_MODIFIED]], $formId);
......@@ -201,10 +217,8 @@ class FormAsFile
* @param string $absoluteFormFilePath
* @throws \UserFormException
*/
private static function backupFormFile(string $absoluteFormFilePath)
{
if (file_exists($absoluteFormFilePath))
{
private static function backupFormFile(string $absoluteFormFilePath) {
if (file_exists($absoluteFormFilePath)) {
if (!is_readable($absoluteFormFilePath)) {
Thrower::userFormException('Error while trying to backup form file.', "Form file is not readable: $absoluteFormFilePath");
}
......@@ -235,7 +249,7 @@ class FormAsFile
{
$recordFormName = self::formNameFromFormRelatedRecord($recordId, $tableName, $database);
if ($recordFormName !== null) {
if(self::importForm($recordFormName, $database)) {
if (self::importForm($recordFormName, $database)) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => 'Form file was changed.',
ERROR_MESSAGE_TO_DEVELOPER => "Form definition file has been changed. Please close tab and reload the form list and Form-Editor from scratch."]),
......@@ -260,7 +274,7 @@ class FormAsFile
{
self::enforceFormFileWritable($formName, $database);
$pathFileName = self::formPathFileName($formName, $database);
if(file_exists($pathFileName)) {
if (file_exists($pathFileName)) {
self::backupFormFile($pathFileName);
$success = unlink($pathFileName);
if ($success === false) {
......@@ -280,8 +294,7 @@ class FormAsFile
* @param string $tableName
* @return string
*/
public static function errorHintFormImport (string $tableName = TABLE_NAME_FORM): string
{
public static function errorHintFormImport(string $tableName = TABLE_NAME_FORM): string {
$message = '';
if (in_array($tableName, [TABLE_NAME_FORM, TABLE_NAME_FORM_ELEMENT])) {
$message .= "Hint: Form definition file might have changed. Please reopen the form list and Form-Editor from scratch.";
......@@ -352,16 +365,14 @@ class FormAsFile
* @param string $sql
* @return bool
*/
public static function isFormQuery(string $sql): bool
{
public static function isFormQuery(string $sql): bool {
// find substrings which start with FROM and are followed by Form or FormElement
preg_match_all('/(?i)FROM(?-i)(.*?)\b(' . TABLE_NAME_FORM . '|' . TABLE_NAME_FORM_ELEMENT . ')\b/s', $sql, $matches);
// Check if no other SQL keywords are in between FROM and the table name
$keywordsAfterFrom = ['WHERE', 'GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'LIMIT', 'FOR', 'INTO'];
foreach($matches[0] as $match)
{
if(!OnString::containsOneOfWords($keywordsAfterFrom, $match)) {
foreach ($matches[0] as $match) {
if (!OnString::containsOneOfWords($keywordsAfterFrom, $match)) {
return true;
}
}
......@@ -417,11 +428,11 @@ class FormAsFile
self::exportForm($formNameDB, $database);
}
if ($deleteFiles) {
$formFileNames = self::formFileNames($database);
$filesToDelete = array_diff($formFileNames, $formNamesDB);
foreach ($filesToDelete as $fileToDelete) {
self::deleteFormFile($fileToDelete, $database, "Export all forms from database. No form with name '$fileToDelete' in database.");
}
$formFileNames = self::formFileNames($database);
$filesToDelete = array_diff($formFileNames, $formNamesDB);
foreach ($filesToDelete as $fileToDelete) {
self::deleteFormFile($fileToDelete, $database, "Export all forms from database. No form with name '$fileToDelete' in database.");
}
}
}
......@@ -437,8 +448,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function insertFormElement(array $values, int $formId, Database $database): int
{
private static function insertFormElement(array $values, int $formId, Database $database): int {
// filter allowed formElement columns (remove id, formId, feIdContainer)
$formElementSchema = $database->getTableDefinition(TABLE_NAME_FORM_ELEMENT);
$formElementColumns = array_column($formElementSchema, 'Field');
......@@ -471,7 +481,7 @@ class FormAsFile
{
$formFromDb = $database->selectFormByName($formName, [F_ID, F_FILE_STATS]);
if ($keepIfNeverExported && (isset($formFromDb[F_ID]) && !isset($formFromDb[F_FILE_STATS])) || (isset($formFromDb[F_FILE_STATS]) && !self::isValidFileStats($formFromDb[F_FILE_STATS]))) {
if ($keepIfNeverExported && (isset($formFromDb[F_ID]) && !isset($formFromDb[F_FILE_STATS])) || (isset($formFromDb[F_FILE_STATS]) && !self::isValidFileStats($formFromDb[F_FILE_STATS]))) {
// export form instead of deleting since it was never exported before
self::exportForm($formName, $database);
} else if (array_key_exists(F_ID, $formFromDb)) {
......@@ -487,7 +497,7 @@ class FormAsFile
* @return bool
*/
private static function isValidFileStats(string $fileStats) {
return substr($fileStats, 0, 1 ) === '{';
return substr($fileStats, 0, 1) === '{';
}
/**
......@@ -522,8 +532,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function backupFormDb(int $formId, Database $database)
{
private static function backupFormDb(int $formId, Database $database) {
list($formName, $formId, $formJson) = self::formToJson('', $database, $formId);
$absoluteBackupFilePath = self::newBackupPathFileName($formName, 'db');
HelperFile::file_put_contents($absoluteBackupFilePath, $formJson);
......@@ -557,9 +566,8 @@ class FormAsFile
* @param string $pathFileName
* @return false|string
*/
private static function formFileStatsJson(string $pathFileName)
{
clearstatcache (true, $pathFileName);
private static function formFileStatsJson(string $pathFileName) {
clearstatcache(true, $pathFileName);
$stats = stat($pathFileName);
if ($stats === false) {
return false;
......@@ -582,8 +590,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function formPathFileName(string $formName, Database $database): string
{
private static function formPathFileName(string $formName, Database $database): string {
// validate form name
if (!HelperFile::isValidFileName($formName)) {
throw new \UserFormException(json_encode([
......@@ -604,8 +611,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function formPath(Database $database): string
{
private static function formPath(Database $database): string {
$absoluteFormPath = Path::absoluteProject(Path::projectToForm());
if (!is_dir($absoluteFormPath)) {
......@@ -633,8 +639,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function queryAllFormNames(Database $database): array
{
private static function queryAllFormNames(Database $database): array {
$NAME = F_NAME;
$FORM = TABLE_NAME_FORM;
return array_column($database->sql("SELECT `$NAME` FROM `$FORM`", ROW_REGULAR), $NAME);
......@@ -649,8 +654,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function formFileNames(Database $database): array
{
private static function formFileNames(Database $database): array {
$formPath = self::formPath($database);
$files = scandir($formPath);
if ($files === false) {
......@@ -684,8 +688,7 @@ class FormAsFile
* @throws \DbException
* @throws \UserFormException
*/
private static function formToJson(string $formName, Database $database, ?int $formId = null): array
{
private static function formToJson(string $formName, Database $database, ?int $formId = null): array {
// Get form from DB (either by id or by name)
if ($formId !== null) {
list($sql, $parameterArray) = SqlQuery::selectFormById($formId);
......@@ -768,8 +771,7 @@ class FormAsFile
* @return string
* @throws \UserFormException
*/
private static function newBackupPathFileName(string $formName, string $tag): string
{
private static function newBackupPathFileName(string $formName, string $tag): string {
// create backup path if not exists
$absoluteBackupPath = Path::absoluteProject(Path::projectToForm(), Path::FORM_TO_FORM_BACKUP);
if (!is_dir($absoluteBackupPath)) {
......@@ -785,7 +787,7 @@ class FormAsFile
$index = 1;
while (file_exists($absoluteBackupFilePath)) {
$absoluteBackupFilePath = Path::join($absoluteBackupPath, $formName . '.json.' . date('Y-m-d_H-i-s') . ".$index.$tag");
$index ++;
$index++;
if ($index > 20) {
Thrower::userFormException('Error while trying to backup form file.', 'Infinite loop.');
}
......
......@@ -1475,7 +1475,7 @@ class Support {
}
/**
* @param $mode
* @param $mode // MODE_ENCODE|MODE_DECODE|MODE_NONE
* @param $data
* @return string
* @throws \UserFormException
......
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