Commit 8db50d60 authored by Carsten  Rose's avatar Carsten Rose
Browse files

#3385 / templateGroup: insert/update/delete non primary records

Manual.rst: update doc how to insert/update/delete non primary templateGroup records.
FormAction.php: removed $templateGroupIndex - solved implicit by defining a LIMIT on 'slaveId' . Implemented '%D' (one below %d). Implemented FE_SQL_HONOR_FORM_ELEMENTS - reduces unecassary SQL queries.
HelperFormElement.php: moved function 'explodeTemplateGroupElements()' to 'QuickFormQuery.php'
Database.php: remove call to explodeTemplateGroupElements() - not necessary at that place.
QuickFormQuery.php: fill STORE_RECORD during Formload - to read templateGroup records very early. Local copy of `getNativeFormElements()`, new `explodeTemplateGroupElements()`
parent b15a9f95
...@@ -1266,7 +1266,7 @@ Type: templateGroup ...@@ -1266,7 +1266,7 @@ Type: templateGroup
*TemplateGroups* will be used to create a series of grouped (by the given *templateGroup*) *FormElements*. *TemplateGroups* will be used to create a series of grouped (by the given *templateGroup*) *FormElements*.
FormElements can be assigned to a *templateGroup*. These *templateGroup* will be rendered upto *n*-times. On 'form load' *FormElements* can be assigned to a *templateGroup*. These *templateGroup* will be rendered upto *n*-times. On 'form load'
only a single (=first) copy of the *templateGroup* will be shown. Below the last copy of the *templateGroup* an 'add'-button is only a single (=first) copy of the *templateGroup* will be shown. Below the last copy of the *templateGroup* an 'add'-button is
shown. If the user click on it, an additional copy of the *templateGroup* is displayed. This can be repeated up to shown. If the user click on it, an additional copy of the *templateGroup* is displayed. This can be repeated up to
*templateGroup.maxLength* times. Also, the user can 'remove' previously created copies by clicking on a remove button near *templateGroup.maxLength* times. Also, the user can 'remove' previously created copies by clicking on a remove button near
...@@ -1294,6 +1294,43 @@ The placeholder `%d` can also be used in the *FormElement.label* ...@@ -1294,6 +1294,43 @@ The placeholder `%d` can also be used in the *FormElement.label*
Example of styling the Add/ Delete Button: :ref:`example_class_template_group` Example of styling the Add/ Delete Button: :ref:`example_class_template_group`
Column: primary record
''''''''''''''''''''''
If the columns `<name>%d` are real columns on the primary table, saving and delete (=empty string) are done automatically.
E.g. if there are upto five elements `grade1, ..., grade5` and the user inputs only the first three, the remaining will be set
to an empty string.
Column: non primary record
''''''''''''''''''''''''''
Additional logic is required to load, update, insert and/or delete slave records.
Load
;;;;
On each native *FormElement* of the *templateGroup* define a SQL query in the *FormElement.value* field. The query has to
select **all** values of all possible existing copies of the *FormElement* - therefore the exclamation mark is necessary.
Also define the order. E.g. *FormElement.value*::
{{!SELECT street FROM Address WHERE personId={{id}} ORDER BY id}}
Insert / Update / Delete
;;;;;;;;;;;;;;;;;;;;;;;;
Add an *action* record, type='afterSave', and assign the record to the given *templateGroup*.
In the parameter field define: ::
slaveId = {{SELECT id FROM Address WHERE personId={{id}} ORDER BY id LIMIT %D,1}}
sqlHonorFormElements = city%d, street%d
sqlUpdate = {{UPDATE Address SET city='{{city%d:FE:alnumx:s}}', street='{{street%d:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1}}
sqlInsert = {{INSERT INTO Address (`personId`, `city`, `street`) VALUES ({{id}}, '{{city%d:FE:alnumx:s}}' , '{{street%d:FE:alnumx:s}}') }}
sqlDelete = {{DELETE FROM Address WHERE id={{slaveId}} LIMIT 1}}
The `slaveId` needs attention: the placeholder `%d` starts always at 1. The `LIMIT` directive starts at 0 - therefore
use `%D` instead of `%d`, cause `%D` is always one below `%d` - but can **only** be used on the action element.
Class: Native Class: Native
------------- -------------
...@@ -2109,11 +2146,19 @@ sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter ...@@ -2109,11 +2146,19 @@ sqlBefore / sqlInsert / sqlUpdate / sqlDelete / sqlAfter
* *requiredList* - List of `native`-*FormElement*: only if all of those elements are filled, the current * *requiredList* - List of `native`-*FormElement*: only if all of those elements are filled, the current
`action`-*FormElement* will be processed. `action`-*FormElement* will be processed.
* *sqlBefore*: always fired (before any *sqlInsert* or *sqlUpdate*) * *sqlBefore*: always fired (before any *sqlInsert*, *sqlUpdate*, ..)
* *sqlInsert*: fired if *slaveId* = `0` or *slaveId* = `''`. * *sqlInsert*: fired if *slaveId* == `0` or *slaveId* == `''`.
* *sqlUpdate*: fired if *slaveId* > `0`: * *sqlUpdate*: fired if *slaveId* > `0`.
* *sqlDelete*: always fired (after *sqlInsert* or *sqlUpdate*) - the definition, when this query is fired, might change in the future. * *sqlDelete*: fired if *slaveId* > `0`, after *sqlInsert* or *sqlUpdate*. Be carefull not to delete filled records!
Always add a check, if values given, not to delete the record! *sqlHonorFormElements* helps to skip such checks.
* *sqlAfter*: always fired (after *sqlInsert*, *sqlUpdate* or *sqlDelete*). * *sqlAfter*: always fired (after *sqlInsert*, *sqlUpdate* or *sqlDelete*).
* *sqlHonorFormElements*: list of *FormElement* names (this parameter is optional).
* If one of the named *FormElements* is not empty:
* fire *sqlInsert* if *slaveId* == `0`,
* fire *sqlUpdate* if *slaveId* > `0`
* If all of the named *FormElements* are empty:
* fire *sqlDelete* if *slaveId* > `0`
Example Example
''''''' '''''''
...@@ -2131,14 +2176,46 @@ Situation 1: master.xId=slave.id (1:1) ...@@ -2131,14 +2176,46 @@ Situation 1: master.xId=slave.id (1:1)
* If the automatic update of the master record is not suitable, the action element should have no name or a name * If the automatic update of the master record is not suitable, the action element should have no name or a name
which does not exist as a column of the master record. Define `slaveId={{SELECT id ...}}` which does not exist as a column of the master record. Define `slaveId={{SELECT id ...}}`
* Two *FormElements* `myStreet` and `myCity`:
* Without *sqlHonorFormElements*. Parameter: ::
sqlInsert = INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}')
sqlUpdate = UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1
sqlDelete = DELETE FROM address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1
* With *sqlHonorFormElements*. Parameter: ::
sqlHonorFormElements = myStreet, myCity
sqlInsert = INSERT INTO address (`street`, `city`) VALUES ('{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}')
sqlUpdate = UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1
sqlDelete = DELETE FROM address WHERE id={{slaveId}} LIMIT 1
Situation 2: master.id=slave.xId (1:n) Situation 2: master.id=slave.xId (1:n)
* Name the action element different to any columnname of the master record (or no name). * Name the action element *different* to any columnname of the master record (or no name).
* Determine the slaveId: `slaveId={{SELECT id FROM slave WHERE slave.xxx={{...}} LIMIT 1}}` * Determine the slaveId: `slaveId={{SELECT id FROM slave WHERE slave.xxx={{...}} LIMIT 1}}`
* {{slaveId}} == 0 ? 'sqlInsert' will be fired. * {{slaveId}} == 0 ? 'sqlInsert' will be fired.
* {{slaveId}} != 0 ? 'sqlUpdate' will be fired. * {{slaveId}} != 0 ? 'sqlUpdate' will be fired.
* Two *FormElements* `myStreet` and `myCity`. The `person` is the master record, `address` is the slave:
* Without *sqlHonorFormElements*. Parameter: ::
slaveId = SELECT id FROM address WHERE personId={{id}} ORDER BY id LIMIT 1
sqlInsert = INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}')
sqlUpdate = UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1
sqlDelete = DELETE FROM address WHERE id={{slaveId}} AND '{{myStreet:FE:alnumx:s}}'='' AND '{{myCity:FE:alnumx:s}}'='' LIMIT 1
* With *sqlHonorFormElements*. Parameter: ::
slaveId = SELECT id FROM address WHERE personId={{id}} ORDER BY id LIMIT 1
sqlHonorFormElements = myStreet, myCity
sqlInsert = INSERT INTO address (`personId`, `street`, `city`) VALUES ({{id}}, '{{myStreet:FE:alnumx:s}}', '{{myCity:FE:alnumx:s}}')
sqlUpdate = UPDATE address SET `street` = '{{myStreet:FE:alnumx:s}}', `city` = '{{myCity:FE:alnumx:s}}' WHERE id={{slaveId}} LIMIT 1
sqlDelete = DELETE FROM address WHERE id={{slaveId}} LIMIT 1
Type: sendmail Type: sendmail
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
......
...@@ -529,7 +529,7 @@ abstract class AbstractBuildForm { ...@@ -529,7 +529,7 @@ abstract class AbstractBuildForm {
$config = OnArray::getArrayItems($formElement, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES]); $config = OnArray::getArrayItems($formElement, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES]);
$config = $this->evaluate->parseArray($config); $config = $this->evaluate->parseArray($config);
if($formElement[FE_LDAP_USE_BIND_CREDENTIALS]==1) { if ($formElement[FE_LDAP_USE_BIND_CREDENTIALS] == 1) {
$config[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM); $config[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
$config[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM); $config[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
} }
...@@ -937,7 +937,7 @@ abstract class AbstractBuildForm { ...@@ -937,7 +937,7 @@ abstract class AbstractBuildForm {
FE_TYPEAHEAD_LIMIT => $formElement[FE_TYPEAHEAD_LIMIT], FE_TYPEAHEAD_LIMIT => $formElement[FE_TYPEAHEAD_LIMIT],
]; ];
if($formElement[FE_LDAP_USE_BIND_CREDENTIALS]=='1') { if ($formElement[FE_LDAP_USE_BIND_CREDENTIALS] == '1') {
$arr[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM); $arr[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
$arr[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM); $arr[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
} }
...@@ -3025,7 +3025,8 @@ EOT; ...@@ -3025,7 +3025,8 @@ EOT;
} }
$default = $this->store->getStore(STORE_TABLE_DEFAULT); // current defaults $default = $this->store->getStore(STORE_TABLE_DEFAULT); // current defaults
// evaluate FE_VALUE on all templateGroup FormElements. // evaluate FE_VALUE on all templateGroup FormElements: After this call, in case of non-primary FEs, FE_VALUE
// might contain an array of values for all copies of the current FE.
$maxForeignRecords = $this->templateGroupDoValue(); $maxForeignRecords = $this->templateGroupDoValue();
$lastFilled = 0; // Marker if there is at least one element per copy who is filled. $lastFilled = 0; // Marker if there is at least one element per copy who is filled.
...@@ -3035,16 +3036,16 @@ EOT; ...@@ -3035,16 +3036,16 @@ EOT;
// Per copy, iterate over all templateGroup FormElements. // Per copy, iterate over all templateGroup FormElements.
foreach ($this->feSpecNative as $fe) { foreach ($this->feSpecNative as $fe) {
$columnName = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_NAME]); $fe[FE_NAME] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_NAME]);
$fe[FE_LABEL] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_LABEL]); $fe[FE_LABEL] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_LABEL]);
$fe[FE_NOTE] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_NOTE]); $fe[FE_NOTE] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_NOTE]);
$columnName = $fe[FE_NAME];
// Column of primary table? // Column of primary table?
if (isset($record[$columnName])) { if (isset($record[$columnName])) {
if ($record[$columnName] != $default[$columnName]) { if ($record[$columnName] != $default[$columnName]) {
$lastFilled = max($ii, $lastFilled); $lastFilled = max($ii, $lastFilled);
} }
$fe[FE_NAME] = $columnName;
} else { } else {
$lastFilled = max($maxForeignRecords, $lastFilled); $lastFilled = max($maxForeignRecords, $lastFilled);
...@@ -3053,6 +3054,7 @@ EOT; ...@@ -3053,6 +3054,7 @@ EOT;
} }
// $fe[FE_TEMPLATE_GROUP_CURRENT_IDX] = $ii; // $fe[FE_TEMPLATE_GROUP_CURRENT_IDX] = $ii;
} }
$feSpecNativeCopy[$ii - 1][] = $fe; // Build array with current copy of templateGroup. $feSpecNativeCopy[$ii - 1][] = $fe; // Build array with current copy of templateGroup.
} }
...@@ -3089,7 +3091,10 @@ EOT; ...@@ -3089,7 +3091,10 @@ EOT;
/** /**
* Evaluate for all FormElements of the current templateGroup the field FE_VALUE. * Evaluate for all FormElements of the current templateGroup the field FE_VALUE.
* If the specific FormElement is not a real column of the primary table, than the value is probably a '{{!SELECT ...' * If the specific FormElement is not a real column of the primary table, than the value is probably a '{{!SELECT ...'
* Statement, that one will be fired. Additional the maximum count of all select rows will be determined and returned. * statement, that one will be fired.
* In case of an non-primary FE, the result array are the values for the copies of the specific FE.
*
* Additional the maximum count of all select rows will be determined and returned.
* *
* @return int max number of records in FormElement[FE_VALUE] over all FormElements. * @return int max number of records in FormElement[FE_VALUE] over all FormElements.
* @throws UserFormException * @throws UserFormException
......
...@@ -644,6 +644,7 @@ const FE_SQL_BEFORE = 'sqlBefore'; // Action: Always fired ...@@ -644,6 +644,7 @@ const FE_SQL_BEFORE = 'sqlBefore'; // Action: Always fired
const FE_SQL_UPDATE = 'sqlUpdate'; // Action: Update Statement for slave record const FE_SQL_UPDATE = 'sqlUpdate'; // Action: Update Statement for slave record
const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave record. const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave record.
const FE_SQL_DELETE = 'sqlDelete'; // Action: Delete Statement to delete unused slave record. const FE_SQL_DELETE = 'sqlDelete'; // Action: Delete Statement to delete unused slave record.
const FE_SQL_HONOR_FORM_ELEMENTS = 'sqlHonorFormElements'; // Action: Honor given list of FormElements for sqlInsert|Update|Delete
const FE_EDITOR_PREFIX = 'editor-'; // TinyMCE configuration settings. const FE_EDITOR_PREFIX = 'editor-'; // TinyMCE configuration settings.
const FE_SENDMAIL_TO = 'sendMailTo'; // Receiver email adresses. Separate multiple by comma. const FE_SENDMAIL_TO = 'sendMailTo'; // Receiver email adresses. Separate multiple by comma.
const FE_SENDMAIL_CC = 'sendMailCc'; // CC Receiver email adresses. Separate multiple by comma. const FE_SENDMAIL_CC = 'sendMailCc'; // CC Receiver email adresses. Separate multiple by comma.
...@@ -672,6 +673,7 @@ const FE_TEMPLATE_GROUP_REMOVE_TEXT = 'tgRemoveText'; ...@@ -672,6 +673,7 @@ const FE_TEMPLATE_GROUP_REMOVE_TEXT = 'tgRemoveText';
const FE_TEMPLATE_GROUP_CLASS = 'tgClass'; const FE_TEMPLATE_GROUP_CLASS = 'tgClass';
const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5; const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5;
const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d'; const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d';
const FE_TEMPLATE_GROUP_NAME_PATTERN_0 = '%D';
const FE_TEMPLATE_GROUP_CURRENT_IDX = 'tgCurentIndex'; const FE_TEMPLATE_GROUP_CURRENT_IDX = 'tgCurentIndex';
const FE_BUTTON_CLASS = 'buttonClass'; const FE_BUTTON_CLASS = 'buttonClass';
const FE_LDAP_SERVER = F_LDAP_SERVER; const FE_LDAP_SERVER = F_LDAP_SERVER;
......
...@@ -650,9 +650,6 @@ class Database { ...@@ -650,9 +650,6 @@ class Database {
// Check for retype FormElements which have to duplicated. // Check for retype FormElements which have to duplicated.
$feSpecNative = HelperFormElement::duplicateRetypeElements($feSpecNative); $feSpecNative = HelperFormElement::duplicateRetypeElements($feSpecNative);
// Check for templateGroup Elements to explode them
$feSpecNative = HelperFormElement::explodeTemplateGroupElements($feSpecNative);
// Copy Attributes to FormElements // Copy Attributes to FormElements
$feSpecNative = HelperFormElement::copyAttributesToFormElements($formSpec, $feSpecNative); $feSpecNative = HelperFormElement::copyAttributesToFormElements($formSpec, $feSpecNative);
......
...@@ -226,7 +226,9 @@ class QuickFormQuery { ...@@ -226,7 +226,9 @@ class QuickFormQuery {
$fillStoreForm->process(); $fillStoreForm->process();
} }
$formName = $this->loadFormSpecification($formMode, $foundInStore); $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT);
$formName = $this->loadFormSpecification($formMode, $recordId, $foundInStore);
if ($formName === false && $formMode !== FORM_DELETE) { if ($formName === false && $formMode !== FORM_DELETE) {
// No form found: do nothing // No form found: do nothing
return ''; return '';
...@@ -247,7 +249,6 @@ class QuickFormQuery { ...@@ -247,7 +249,6 @@ class QuickFormQuery {
$this->formSpec[F_TABLE_NAME] = $table; $this->formSpec[F_TABLE_NAME] = $table;
} }
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT);
// For 'new' record always create a new Browser TAB-uniq (for this current form, nowhere else used) SIP. // For 'new' record always create a new Browser TAB-uniq (for this current form, nowhere else used) SIP.
// With such a Browser TAB-uniq SIP, multiple Browser TABs and following repeated NEWs are easily implemented. // With such a Browser TAB-uniq SIP, multiple Browser TABs and following repeated NEWs are easily implemented.
if (!$sipFound || ($formMode == FORM_LOAD && $recordId == 0)) { if (!$sipFound || ($formMode == FORM_LOAD && $recordId == 0)) {
...@@ -369,13 +370,14 @@ class QuickFormQuery { ...@@ -369,13 +370,14 @@ class QuickFormQuery {
* Loaded 'native' FormElements are in $this->feSpecNative * Loaded 'native' FormElements are in $this->feSpecNative
* *
* @param string $mode FORM_LOAD|FORM_SAVE|FORM_UPDATE * @param string $mode FORM_LOAD|FORM_SAVE|FORM_UPDATE
* @param int $recordId
* @param string $foundInStore * @param string $foundInStore
* @return bool|string if found the formName, else 'false'. * @return bool|string if found the formName, else 'false'.
* @throws CodeException * @throws CodeException
* @throws DbException * @throws DbException
* @throws UserFormException * @throws UserFormException
*/ */
private function loadFormSpecification($mode, &$foundInStore = '') { private function loadFormSpecification($mode, $recordId, &$foundInStore = '') {
// formName // formName
if (false === ($formName = $this->getFormName($mode, $foundInStore))) { if (false === ($formName = $this->getFormName($mode, $foundInStore))) {
...@@ -413,6 +415,9 @@ class QuickFormQuery { ...@@ -413,6 +415,9 @@ class QuickFormQuery {
$this->formSpec = $formSpec; $this->formSpec = $formSpec;
// this is needed for filling templateGroup records with their default values
$this->fillStoreWithRecord($this->formSpec[F_TABLE_NAME], $recordId, STORE_RECORD);
// Clear // Clear
$this->store->setVar(SYSTEM_FORM_ELEMENT, '', STORE_SYSTEM); $this->store->setVar(SYSTEM_FORM_ELEMENT, '', STORE_SYSTEM);
...@@ -431,7 +436,8 @@ class QuickFormQuery { ...@@ -431,7 +436,8 @@ class QuickFormQuery {
case FORM_SAVE: case FORM_SAVE:
case FORM_UPDATE: case FORM_UPDATE:
// $this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_ALL_CONTAINER, ['no', $this->formSpec["id"], 'native'], $this->formSpec); // $this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_ALL_CONTAINER, ['no', $this->formSpec["id"], 'native'], $this->formSpec);
$this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec["id"]], $this->formSpec);
$this->feSpecNative = $this->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec["id"]], $this->formSpec);
break; break;
case FORM_DELETE: case FORM_DELETE:
...@@ -445,6 +451,79 @@ class QuickFormQuery { ...@@ -445,6 +451,79 @@ class QuickFormQuery {
return $formName; return $formName;
} }
/**
* Depending on $sql reads FormElements to a specific container or all. Preprocess all FormElements.
* This code is dirty: the nearly same function exists in class 'Database' - the difference is only 'explodeTemplateGroupElements()'.
*
* @param string $sql SQL_FORM_ELEMENT_SPECIFIC_CONTAINER | SQL_FORM_ELEMENT_ALL_CONTAINER
* @param array $param Parameter which matches the prepared statement in $sql
* @param array $formSpec Main FormSpec to copy generic parameter to FormElements
* @return array|int
* @throws \qfq\CodeException
* @throws \qfq\DbException
*/
public function getNativeFormElements($sql, array $param, $formSpec) {
$feSpecNative = $this->db->sql($sql, ROW_REGULAR, $param);
// Explode and Do $FormElement.parameter
HelperFormElement::explodeParameterInArrayElements($feSpecNative, FE_PARAMETER);
// Check for retype FormElements which have to duplicated.
$feSpecNative = HelperFormElement::duplicateRetypeElements($feSpecNative);
// Check for templateGroup Elements to explode them
$feSpecNative = $this->explodeTemplateGroupElements($feSpecNative);
// Copy Attributes to FormElements
$feSpecNative = HelperFormElement::copyAttributesToFormElements($formSpec, $feSpecNative);
return $feSpecNative;
}
/**
* Iterate over all FormElements in $elements. If a row has a column NAME_TG_COPIES, copy those elements NAME_TG_COPIES-times.
* Adjust FE_TEMPLATE_GROUP_NAME_PATTERN (='%d') with current count on column FE_NAME and FE_LABEL.
*
* This code is dirty: only to get JSON value, we have to initialize the STORE_RECORD (done earlier) to be capable to
* parse fe[FE_VALUE], which probably contains as string like '{{!SELECT value FROM table WHERE xId={{id}} ORDER BY id}}' -
* the {{id}} needs to be replaced by the current recordId (primary record).
*
* Attention: The resulting order of the FormElements, is not the same as on the Form during FormLoad!
*
* @param array $elements
* @return array
*/
private function explodeTemplateGroupElements(array $elements) {
$new = array();
// No FormElements or no NAME_TG_COPIES column: nothing to do, return.
if ($elements == array() || count($elements) == 0 || !isset($elements[0][NAME_TG_COPIES])) {
return $elements;
}
// Iterate over all
foreach ($elements as $row) {
if (isset($row[NAME_TG_COPIES]) && $row[NAME_TG_COPIES] > 0) {
$row[FE_VALUE] = $this->eval->parse($row[FE_VALUE]);
for ($ii = 1; $ii <= $row[NAME_TG_COPIES]; $ii++) {
$tmpRow = $row;
if (is_array($row[FE_VALUE])) {
$tmpRow[FE_VALUE] = ($ii <= count($row[FE_VALUE])) ? current($row[FE_VALUE][$ii - 1]) : '';
}
unset($tmpRow[NAME_TG_COPIES]);
$tmpRow[FE_NAME] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $tmpRow[FE_NAME]);
$tmpRow[FE_LABEL] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $tmpRow[FE_LABEL]);
$new[] = $tmpRow;
}
} else {
$new[] = $row;
}
}
return $new;
}
/** /**
* Get the formName from STORE_TYPO3 (bodytext), STORE_SIP or by STORE_CLIENT (URL). * Get the formName from STORE_TYPO3 (bodytext), STORE_SIP or by STORE_CLIENT (URL).
* *
......
...@@ -13,6 +13,7 @@ require_once(__DIR__ . '/../Database.php'); ...@@ -13,6 +13,7 @@ require_once(__DIR__ . '/../Database.php');
require_once(__DIR__ . '/../store/Store.php'); require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../Evaluate.php'); require_once(__DIR__ . '/../Evaluate.php');
require_once(__DIR__ . '/../report/Sendmail.php'); require_once(__DIR__ . '/../report/Sendmail.php');
require_once(__DIR__ . '/../helper/HelperFormElement.php');
/** /**
* Class formAction * Class formAction
...@@ -65,7 +66,7 @@ class FormAction { ...@@ -65,7 +66,7 @@ class FormAction {
* @throws DbException * @throws DbException
* @throws UserFormException * @throws UserFormException
*/ */
public function elements($recordId, array $feSpecAction, $feTypeList, $templateGroupIndex = 0) { public function elements($recordId, array $feSpecAction, $feTypeList) {
$flagModified = false; $flagModified = false;
...@@ -85,18 +86,20 @@ class FormAction { ...@@ -85,18 +86,20 @@ class FormAction {
// Process templateGroup action elements // Process templateGroup action elements
if (isset($fe[FE_ID_CONTAINER]) && $fe[FE_ID_CONTAINER] > 0) { if (isset($fe[FE_ID_CONTAINER]) && $fe[FE_ID_CONTAINER] > 0) {
// Get native 'templateGroup'-FE - to retrieve MAX_LENGTH
$feTemplateGroup = $this->db->sql(SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID, ROW_REGULAR, [$fe[FE_ID_CONTAINER]]); $feTemplateGroup = $this->db->sql(SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID, ROW_REGULAR, [$fe[FE_ID_CONTAINER]]);
if (count($feTemplateGroup) == 1) { if (count($feTemplateGroup) == 1) {
$fe[FE_ID_CONTAINER] = 0; $fe[FE_ID_CONTAINER] = 0;
for ($ii = 1; $ii < $feTemplateGroup[0][FE_MAX_LENGTH]; $ii++) { for ($ii = 1; $ii <= $feTemplateGroup[0][FE_MAX_LENGTH]; $ii++) {
$feNew = OnArray::arrayValueReplace($fe, FE_TEMPLATE_GROUP_NAME_PATTERN, $ii); $feNew = OnArray::arrayValueReplace($fe, FE_TEMPLATE_GROUP_NAME_PATTERN, $ii);
if ($this->elements($recordId, [$feNew], $feTypeList, $ii)) { $feNew = OnArray::arrayValueReplace($feNew, FE_TEMPLATE_GROUP_NAME_PATTERN_0, $ii - 1);
if ($this->elements($recordId, [$feNew], $feTypeList)) {
$flagModified = true; $flagModified = true;
} }
} }
} else { } else {
// At the moment 'action' elements have to point to a templateGroup - nothing else is defined. Break if there is somethin else // At the moment 'action' elements have to point to a templateGroup - nothing else is defined. Break if there is something else
throw new UserFormException("Expect a 'templateGroup' record in FormElement.id=", $fe[FE_ID_CONTAINER], ERROR_RECORD_NOT_FOUND); throw new UserFormException("Expect a 'templateGroup' record in FormElement.id=", $fe[FE_ID_CONTAINER], ERROR_RECORD_NOT_FOUND);
} }
continue; // skip to next FormElement continue; // skip to next FormElement
...@@ -142,7 +145,7 @@ class FormAction { ...@@ -142,7 +145,7 @@ class FormAction {
$this->validate($fe); $this->validate($fe);
$this->doSlave($fe, $recordId, $templateGroupIndex); $this->doSlave($fe, $recordId);
$flagModified = true; $flagModified = true;
} }
...@@ -267,26 +270,15 @@ class FormAction { ...@@ -267,26 +270,15 @@ class FormAction {
* *
* @param array $fe * @param array $fe
* @param int $recordId * @param int $recordId
* @param int $templateGroupIndex
* @return int * @return int
* @throws CodeException * @throws CodeException
* @throws DbException * @throws DbException
* @throws UserFormException * @throws UserFormException
*/ */
private function doSlave(array $fe, $recordId, $templateGroupIndex) { private function doSlave(array $fe, $recordId) {
// Get the slaveId // Get the slaveId
$slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]); $slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]);
if ($templateGroupIndex > 0) {
if (is_array($slaveId)) {
// Select the n'th id of the array
$slaveId = isset($slaveId[$templateGroupIndex]) ? $slaveId[$templateGroupIndex] : 0;
} else {
throw new UserFormException("Result not an arry. SQL Statement for 'slaveId' in TemplateGroup should return an array with all 'slaveId's.", ERROR_EXPECTED_ARRAY);
}
} elseif (is_array($slaveId)) {
throw new UserFormException("Result not a single value. SQL Statement for 'slaveId' in should a single value.", ERROR_UNEXPECTED_TYPE);
}
if ($slaveId === '' && $fe[FE_NAME] !== '') { if ($slaveId === '' && $fe[FE_NAME] !== '') {
// if the current action element has the same name as a real master record column: take that value as an id // if the current action element has the same name as a real master record column: take that value as an id
...@@ -303,24 +295,41 @@ class FormAction { ...@@ -303,24 +295,41 @@ class FormAction {
// If given: fire a sqlBefore query // If given: fire a sqlBefore query
$this->evaluate->parse($fe[FE_SQL_BEFORE]); $this->evaluate->parse($fe[FE_SQL_BEFORE]);
$doInsert = ($slaveId == 0);
$doUpdate = ($slaveId != 0);
$doDelete = ($slaveId != 0);
$flagHonor = isset($fe[FE_SQL_HONOR_FORM_ELEMENTS]) && $fe[FE_SQL_HONOR_FORM_ELEMENTS] != '';
if ($flagHonor) {
$filled = $this->checkFormElements($fe[FE_SQL_HONOR_FORM_ELEMENTS]);
$doInsert = $filled && $doInsert;
$doUpdate = $filled && $doUpdate;
$doDelete = !$filled && $doDelete;
}
// Fire slave query // Fire slave query
if ($slaveId == 0) { if ($doInsert) {
$slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]); $slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]);
// Store the slaveId: might be used later // Store the slaveId: might be used later
$this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true); $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);
} else { }
if ($doUpdate) {
$this->evaluate->parse($fe[FE_SQL_UPDATE]); $this->evaluate->parse($fe[FE_SQL_UPDATE]);
} }
// Fire a delete query
if ($doDelete) {
$this->evaluate->parse($fe[FE_SQL_DELETE]);