Commit 55235fde authored by Marc Egger's avatar Marc Egger
Browse files

Refs #12145 json form editor can load and save

parent 4d342e7d
Pipeline #5094 passed with stages
in 3 minutes and 52 seconds
......@@ -1320,6 +1320,10 @@ const FE_MAX = 'max';
const RETYPE_FE_NAME_EXTENSION = 'RETYPE';
// Save form as Json
const FE_SAVE_FORM_JSON = 'saveFormJson';
const FE_SAVE_FORM_JSON_NAME = 'saveFormJsonName';
const TYPEAHEAD_PLACEHOLDER = '?';
// Values
......@@ -1640,6 +1644,8 @@ const C_HIDE = 'hide';
const COLUMN_WRAP_TOKEN = '+';
const COLUMN_STORE_USER = '=';
const COLUMN_FORM_JSON = 'formJson';
const COLUMN_FORM_JSON_BASE_64 = 'formJsonBase64';
const FORM_NAME_FORM = 'form';
const FORM_NAME_FORM_ELEMENT = 'formElement';
......
......@@ -390,6 +390,11 @@ class FormAction {
// If given: fire a $sqlAfter query
$this->evaluate->parse($fe[FE_SQL_AFTER]);
// If given: save json form
if ($fe[FE_SAVE_FORM_JSON] !== null && $fe[FE_SAVE_FORM_JSON] !== '' && $fe[FE_SAVE_FORM_JSON_NAME] !== null && $fe[FE_SAVE_FORM_JSON_NAME] !== '') {
FormAsFile::jsonToDatabase($this->evaluate->parse($fe[FE_SAVE_FORM_JSON_NAME]), $this->evaluate->parse($fe[FE_SAVE_FORM_JSON]), $this->db);
}
return $rcStatus;
}
......
......@@ -22,7 +22,6 @@ class FormAsFile {
* If the form file can't be read, then the form is deleted from the DB and an exception is thrown.
* If the form exists only in the DB and was never exported, then it is exported.
*
* - Form file location: SYSTEM_FORM_FILE_PATH
* - Recognize form file change: Compare the current file stats with the ones saved in the Form table.
* - Container References: The form file uses names instead if ids to reference container formElements. These references are translated when the formElements are inserted.
* - Ignored keys: The keys 'id', 'formId', 'feIdContainer' are ignored when reading the form file.
......@@ -85,46 +84,71 @@ class FormAsFile {
self::deleteFormDB($formName, $database, "Failed to read form file.");
$fileReadException();
}
$formFromFile = json_decode($fileContents, true);
// import json to database
self::jsonToDatabase($formName, $fileContents, $database, $absoluteFormFilePath, $fileStatsNew);
return true;
}
/**
* Remove the form from the DB and insert it using the given form json.
*
* - Container References: The form json uses names instead if ids to reference container formElements. These references are translated when the formElements are inserted.
* - Ignored keys: The keys 'id', 'formId', 'feIdContainer' are ignored when reading the form json.
* @param string $formName
* @param string $formRawJson
* @param Database $database
* @param string|null $absoluteFormFilePath
* @param string|null $fileStatsNew
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
*/
public static function jsonToDatabase(string $formName, string $formRawJson, Database $database, string $absoluteFormFilePath = null, string $fileStatsNew = null)
{
$formFromJson = json_decode($formRawJson, true);
// form elements exist?
if (!isset($formFromFile[F_FILE_FORM_ELEMENT])) {
Thrower::userFormException('Failed to import form file.', "Json key '" . F_FILE_FORM_ELEMENT . "' in file '$absoluteFormFilePath' is missing.");
if (!isset($formFromJson[F_FILE_FORM_ELEMENT])) {
Thrower::userFormException('Failed to import form json.', "Json key '" . F_FILE_FORM_ELEMENT . "' is missing."
. ($absoluteFormFilePath !== null ? " Form file path: '$absoluteFormFilePath'" : ''));
}
// make sure container names are unique and non-empty
$containerNames = [];
foreach ($formFromFile[F_FILE_FORM_ELEMENT] as $formElementFromFile) {
$keysNotSet = OnArray::keysNotSet([FE_CLASS, FE_NAME], $formElementFromFile);
foreach ($formFromJson[F_FILE_FORM_ELEMENT] as $formElementFromJson) {
$keysNotSet = OnArray::keysNotSet([FE_CLASS, FE_NAME], $formElementFromJson);
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 json.',
"One or more required keys are missing in FormElement definition. Missing keys: " . implode(', ', $keysNotSet)
. ($absoluteFormFilePath !== null ? ". Form file path: '$absoluteFormFilePath'" : ''));
}
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'");
if ($formElementFromJson[FE_CLASS] === FE_CLASS_CONTAINER) {
if (in_array($formElementFromJson[FE_NAME], $containerNames)) {
Thrower::userFormException('Failed to import form json.',
"Multiple formElements of class container with the same name '" . $formElementFromJson[FE_NAME]
. ($absoluteFormFilePath !== null ? ". Form file path: '$absoluteFormFilePath'" : ''));
}
if ($formElementFromFile[FE_NAME] == '') {
Thrower::userFormException('Failed to import form file.',
"Found formElement of class container with empty name in form file '$absoluteFormFilePath'");
if ($formElementFromJson[FE_NAME] == '') {
Thrower::userFormException('Failed to import form json.',
"Found formElement of class container with empty name."
. ($absoluteFormFilePath !== null ? " Form file path: '$absoluteFormFilePath'" : ''));
}
$containerNames[] = $formElementFromFile[FE_NAME];
$containerNames[] = $formElementFromJson[FE_NAME];
}
}
// keep old form ID for deletion later & define temporary name for new form
$formIdOld = $formFromDb[F_ID] ?? null;
// define temporary name for new form
$newFormTempName = $formName . '_FAILED_IMPORT'; // will be renamed at the end
// Insert new Form to DB (after filtering allowed columns and adding column 'name')
$formSchema = $database->getTableDefinition(TABLE_NAME_FORM);
$formColumns = array_column($formSchema, 'Field');
$insertValues = array_filter($formFromFile, function ($columnName) use ($formColumns) {
$insertValues = array_filter($formFromJson, function ($columnName) use ($formColumns) {
return $columnName !== F_ID && in_array($columnName, $formColumns);
}, ARRAY_FILTER_USE_KEY); // array(column => value)
$insertValues[F_NAME] = $newFormTempName;
$insertValues[F_FILE_STATS] = $fileStatsNew;
$insertValues[F_FILE_STATS] = $fileStatsNew ?? 'no file';
list($sqlFormInsert, $parameterArrayFormInsert) = SqlQuery::insertRecord(TABLE_NAME_FORM, $insertValues);
$formIdNew = $database->sql($sqlFormInsert, ROW_REGULAR, $parameterArrayFormInsert);
......@@ -133,39 +157,46 @@ class FormAsFile {
// Insert FormElements to DB and collect container ids
$containerIds = []; // array(container_name => id)
foreach ($formFromFile[F_FILE_FORM_ELEMENT] as &$formElementFromFile) {
$feId = self::insertFormElement($formElementFromFile, $formIdNew, $database);
$formElementFromFile[FE_ID] = $feId;
if ($formElementFromFile[FE_CLASS] === FE_CLASS_CONTAINER) {
$containerIds[$formElementFromFile[FE_NAME]] = $feId;
foreach ($formFromJson[F_FILE_FORM_ELEMENT] as &$formElementFromJson) {
$feId = self::insertFormElement($formElementFromJson, $formIdNew, $database);
$formElementFromJson[FE_ID] = $feId;
if ($formElementFromJson[FE_CLASS] === FE_CLASS_CONTAINER) {
$containerIds[$formElementFromJson[FE_NAME]] = $feId;
}
}
// Update container IDs for each form element which has a container name
foreach ($formFromFile[F_FILE_FORM_ELEMENT] as &$formElementFromFile) {
if (array_key_exists(FE_FILE_CONTAINER_NAME, $formElementFromFile)) {
$containerName = $formElementFromFile[FE_FILE_CONTAINER_NAME];
foreach ($formFromJson[F_FILE_FORM_ELEMENT] as &$formElementFromJson) {
if (array_key_exists(FE_FILE_CONTAINER_NAME, $formElementFromJson)) {
$containerName = $formElementFromJson[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 json.',
"Key '" . FE_FILE_CONTAINER_NAME . "' points to non-existing container with name '$containerName' in definition of formElement with name '" . $formElementFromJson[FE_NAME]
. ($absoluteFormFilePath !== null ? ". Form file path: '$absoluteFormFilePath'" : ''));
}
$containerId = $containerIds[$containerName];
$feId = $formElementFromFile[FE_ID];
$feId = $formElementFromJson[FE_ID];
list($sql, $parameterArray) = SqlQuery::updateRecord(TABLE_NAME_FORM_ELEMENT, [FE_ID_CONTAINER => $containerId], $feId);
$database->sql($sql, ROW_REGULAR, $parameterArray);
}
}
// Delete old form if everything went well
$formFromDb = $database->selectFormByName($formName, [F_ID, F_FILE_STATS]);
$formIdOld = $formFromDb[F_ID] ?? null;
if ($formIdOld !== null) {
self::deleteFormDBWithId($formIdOld, $database, "New version of form $formName was imported successfully. New form id: $formIdNew");
}
// Replace temporary name of new form
list($sql, $parameterArray) = SqlQuery::updateRecord(TABLE_NAME_FORM, [F_NAME => $formName], $formIdNew);
// Replace temporary name of new form and set id to old id
list($sql, $parameterArray) = SqlQuery::updateRecord(TABLE_NAME_FORM, [F_NAME => $formName, F_ID => $formIdOld], $formIdNew);
$database->sql($sql, ROW_REGULAR, $parameterArray);
return true;
// update formId on all new FormElements
$_FormElement = TABLE_NAME_FORM_ELEMENT;
$_formId = FE_FORM_ID;
$sql = "UPDATE `$_FormElement` SET `$_formId`=? WHERE `$_formId`=?";
$database->sql($sql, ROW_REGULAR, [$formIdOld, $formIdNew]);
}
/**
......@@ -436,6 +467,29 @@ class FormAsFile {
}
}
/**
* Return the form as a json string.
*
* @param $columnValue string form id as string.
* @param $database
* @return string form as json.
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
*/
public static function renderColumnFormJson($columnValue, $database): string
{
$formJson = '';
if (!ctype_digit($columnValue)) {
Thrower::userFormException('Rendering Json Form failed.', "The special column '_" . COLUMN_FORM_JSON . "' Expects an integer form id but '$columnValue' given.");
}
$formId = intval($columnValue);
if ($formId !== 0) {
list($_, $_, $formJson) = self::formToJson('', $database, $formId);
}
return $formJson;
}
/**
* Insert formElement to the given form.
* Keys removed before insert: id, formId, feIdContainer
......
......@@ -1293,6 +1293,13 @@ class Report {
$content = Support::wrapTag("<div style='$outerWrapStyle'>", $content);
break;
case COLUMN_FORM_JSON:
$content .= Support::encryptDoubleCurlyBraces(FormAsFile::renderColumnFormJson($columnValue, $this->db));
break;
case COLUMN_FORM_JSON_BASE_64:
$content .= base64_encode(FormAsFile::renderColumnFormJson($columnValue, $this->db));
break;
default :
$flagOutput = false;
......
......@@ -31,7 +31,10 @@ form={{form:SE}}
20 {
# All forms
sql = SELECT CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id) as _pagee
sql = SELECT '<td>'
, CONCAT('p:{{pageAlias:T}}&form=form&r=', f.id) as _pagee
, CONCAT('p:{{pageAlias:T}}&form=formJson&r=', f.id, '|t:json') as _pagee
, '</td>'
, CONCAT(f.name, ' <span class="text-muted">(', f.id, ')</span>')
, QMORE(strip_tags(f.title),50)
, f.tableName
......@@ -50,6 +53,7 @@ form={{form:SE}}
rend = </tr>
fbeg = <td>
fend = </td>
fskipwrap = 1,2,3,4
}
}
......
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