From 769e7cc921220b1ac6edec78f9fda697a0fda80e Mon Sep 17 00:00:00 2001 From: Carsten Rose <carsten.rose@math.uzh.ch> Date: Mon, 20 Mar 2017 22:52:57 +0100 Subject: [PATCH] FormAction.php: started to implement 'save templateGroups with foreign records' OnArray.php: new function 'arrayValueReplace()'. AbstractBuildForm.php: Load foreign values in templatGroups. --- extension/qfq/qfq/AbstractBuildForm.php | 65 ++++++++++++++++++--- extension/qfq/qfq/Constants.php | 2 + extension/qfq/qfq/form/FormAction.php | 43 ++++++++++++-- extension/qfq/qfq/helper/OnArray.php | 17 ++++++ extension/qfq/tests/phpunit/OnArrayTest.php | 15 +++++ 5 files changed, 130 insertions(+), 12 deletions(-) diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 73b1b27b3..8f41eb176 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -406,8 +406,24 @@ abstract class AbstractBuildForm { //In case the current element is a 'RETYPE' element: take the element name of the source FormElement. Needed in the next row to retrieve the default value. $name = (isset($formElement[FE_RETYPE_SOURCE_NAME])) ? $formElement[FE_RETYPE_SOURCE_NAME] : $formElement[FE_NAME]; - // If there is a value explicit defined: take it + $value = ''; + Support::setIfNotSet($formElement, FE_VALUE); + + // If is FormElement['value'] explicit defined: take it + // There are two options: a) single value, b) array of values (template Group) +// if (is_array($formElement[FE_VALUE])) { +// // For Templates Groups, the 'value' has to be defined as '{{!SELECT ...' wich returns all selected records in an array. +// $idx = isset($formElement[FE_TEMPLATE_GROUP_CURRENT_IDX]) ? $formElement[FE_TEMPLATE_GROUP_CURRENT_IDX] - 1 : 0; +// if (isset($formElement[FE_VALUE][$idx]) && is_array($formElement[FE_VALUE][$idx])) { +// $value = current($formElement[FE_VALUE][$idx]); +// if ($value === false) { +// $value = ''; +// } +// } +// } else { $value = $formElement[FE_VALUE]; +// } + if ($value === '') { // Only take the default, if the FE is a real tablecolumn. See #2064 if ($this->store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES) !== false) { @@ -2961,29 +2977,38 @@ EOT; } $default = $this->store->getStore(STORE_TABLE_DEFAULT); // current defaults + // evaluate FE_VALUE on all templateGroup FormElements. + $maxForeignRecords = $this->templateGroupDoValue(); + $lastFilled = 0; // Marker if there is at least one element per copy who is filled. $feSpecNativeCopy = array(); - for ($ii = 1; $ii < $max; $ii++) { + for ($ii = 1; $ii <= $max; $ii++) { - // Per copy, iterate over all templateGroup FormElements + // Per copy, iterate over all templateGroup FormElements. foreach ($this->feSpecNative as $fe) { $columnName = 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_NOTE] = str_replace(FE_TEMPLATE_GROUP_NAME_PATTERN, $ii, $fe[FE_NOTE]); + // Column of primary table? if (isset($record[$columnName])) { if ($record[$columnName] != $default[$columnName]) { - $lastFilled = $ii; + $lastFilled = max($ii, $lastFilled); } + $fe[FE_NAME] = $columnName; + } else { - throw new UserFormException("Not implemented: templateGroup FormElement-columns not found in primary table.", ERROR_NOT_IMPLEMENTED); + $lastFilled = max($maxForeignRecords, $lastFilled); + if (is_array($fe[FE_VALUE]) && isset($fe[FE_VALUE][$ii - 1])) { + $fe[FE_VALUE] = current($fe[FE_VALUE][$ii - 1]); // replace array with current value + } +// $fe[FE_TEMPLATE_GROUP_CURRENT_IDX] = $ii; } - $fe[FE_NAME] = $columnName; $feSpecNativeCopy[$ii - 1][] = $fe; // Build array with current copy of templateGroup. } - // Append $htmlDelete on the last element of all copies,, but not the first. + // Append $htmlDelete on the last element of all copies, but not the first. if ($ii > 1) { // Count defined FormElements in the current templateGroup $last = count($feSpecNativeCopy[$ii - 1]) - 1; @@ -2999,6 +3024,7 @@ EOT; $feSpecNativeSave = $this->feSpecNative; + $lastFilled = min($lastFilled, $max); // It's possible (external records) that there are more fetched values than the maximum allows - skip those. $html = ''; for ($ii = 0; $ii < $lastFilled; $ii++) { $this->feSpecNative = $feSpecNativeCopy[$ii]; @@ -3012,6 +3038,31 @@ EOT; return $html; } + /** + * 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 ...' + * Statement, that one will be fired. 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. + * @throws UserFormException + */ + private function templateGroupDoValue() { + + // Fire 'value' statement + $tgMax = 0; + foreach ($this->feSpecNative as $key => $arr) { + $this->feSpecNative[$key][FE_VALUE] = $this->evaluate->parse($arr[FE_VALUE]); + + if (is_array($this->feSpecNative[$key][FE_VALUE])) { + $cnt = count($this->feSpecNative[$key][FE_VALUE]); + + $tgMax = max($cnt, $tgMax); + } + } + + return $tgMax; + } + abstract public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementName); } \ No newline at end of file diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 92cb305fd..6b959577c 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -50,6 +50,7 @@ const SQL_FORM_ELEMENT_SPECIFIC_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' F const SQL_FORM_ELEMENT_ALL_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.enabled='yes' ORDER BY fe.ord, fe.id"; const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.label, fe.type, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id"; const SQL_FORM_ELEMENT_CONTAINER_TEMPLATE_GROUP = "SELECT fe.id, fe.name, fe.label, fe.maxLength, fe.parameter FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ORDER BY fe.ord, fe.id"; +const SQL_FORM_ELEMENT_TEMPLATE_GROUP = "SELECT * FROM FormElement AS fe WHERE fe.id = ? AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' "; // SANITIZE Classifier const SANITIZE_ALLOW_ALNUMX = "alnumx"; @@ -635,6 +636,7 @@ const FE_TEMPLATE_GROUP_REMOVE_TEXT = 'tgRemoveText'; const FE_TEMPLATE_GROUP_CLASS = 'tgClass'; const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5; const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d'; +const FE_TEMPLATE_GROUP_CURRENT_IDX = 'tgCurentIndex'; const FE_BUTTON_CLASS = 'buttonClass'; const FE_LDAP_SERVER = F_LDAP_SERVER; const FE_LDAP_BASE_DN = F_LDAP_BASE_DN; diff --git a/extension/qfq/qfq/form/FormAction.php b/extension/qfq/qfq/form/FormAction.php index 82c340ff4..8b66072a8 100644 --- a/extension/qfq/qfq/form/FormAction.php +++ b/extension/qfq/qfq/form/FormAction.php @@ -65,13 +65,16 @@ class FormAction { * @throws DbException * @throws UserFormException */ - public function elements($recordId, array $feSpecAction, $feTypeList) { + public function elements($recordId, array $feSpecAction, $feTypeList, $templateGroupIndex = 0) { $flagModified = false; // Iterate over all Action FormElements foreach ($feSpecAction as $fe) { + // Preparation for Log, Debug + $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); + $fe = HelperFormElement::initActionFormElement($fe); // Only process FE elements of types listed in $feTypeList. Skip all other @@ -79,8 +82,25 @@ class FormAction { continue; } - // Preparation for Log, Debug - $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); + // Process templateGroup action elements + if (isset($fe[FE_ID_CONTAINER]) && $fe[FE_ID_CONTAINER] > 0) { + + $feTemplateGroup = $this->db->sql(SQL_FORM_ELEMENT_TEMPLATE_GROUP, ROW_REGULAR, [$fe[FE_ID_CONTAINER]]); + + if (count($feTemplateGroup) == 1) { + $fe[FE_ID_CONTAINER] = 0; + for ($ii = 1; $ii < $feTemplateGroup[0][FE_MAX_LENGTH]; $ii++) { + $feNew = OnArray::arrayValueReplace($fe, FE_TEMPLATE_GROUP_NAME_PATTERN, $ii); + if ($this->elements($recordId, [$feNew], $feTypeList, $ii)) { + $flagModified = true; + } + } + } else { + // At the moment 'action' elements have to point to a templateGroup - nothing else is defined. Break if there is somethin else + throw new UserFormException("Expect a 'templateGroup' record in FormElement.id=", $fe[FE_ID_CONTAINER], ERROR_RECORD_NOT_FOUND); + } + continue; // skip to next FormElement + } switch ($fe[FE_TYPE]) { case FE_TYPE_BEFORE_LOAD: @@ -117,7 +137,7 @@ class FormAction { $this->validate($fe); - $this->doSlave($fe, $recordId); + $this->doSlave($fe, $recordId, $templateGroupIndex); $flagModified = true; } @@ -241,14 +261,27 @@ class FormAction { * Create the slave record. First try to evaluate a slaveId. Depending if the slaveId > 0 choose `sqlUpdate` or `sqlInsert` * * @param array $fe + * @param int $recordId + * @param int $templateGroupIndex * @return int * @throws CodeException + * @throws DbException * @throws UserFormException */ - private function doSlave(array $fe, $recordId) { + private function doSlave(array $fe, $recordId, $templateGroupIndex) { // Get the slaveId $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 the current action element has the same name as a real master record column: take that value as an id diff --git a/extension/qfq/qfq/helper/OnArray.php b/extension/qfq/qfq/helper/OnArray.php index 0d7020128..b60e6fe32 100644 --- a/extension/qfq/qfq/helper/OnArray.php +++ b/extension/qfq/qfq/helper/OnArray.php @@ -228,4 +228,21 @@ class OnArray { return $new; } + /** + * Iterates over an array and replaces all occurences of $search with $replace. Returns the new array. + * + * @param array $src + * @param $search + * @param $replace + * @return array + */ + public static function arrayValueReplace(array $src, $search, $replace ) { + $new = array(); + + foreach($src AS $key => $element) { + $new[$key] = str_replace($search, $replace, $element); + } + + return $new; + } } \ No newline at end of file diff --git a/extension/qfq/tests/phpunit/OnArrayTest.php b/extension/qfq/tests/phpunit/OnArrayTest.php index f2bc6b44e..de04a7ec2 100644 --- a/extension/qfq/tests/phpunit/OnArrayTest.php +++ b/extension/qfq/tests/phpunit/OnArrayTest.php @@ -120,4 +120,19 @@ class OnArrayTest extends \PHPUnit_Framework_TestCase { $this->assertEquals(['a' => 'hello'], OnArray::getArrayItems(['a' => 'hello', 'b' => 'world'], ['a', 'c'], false)); $this->assertEquals(['a' => 'hello', 'c' => ''], OnArray::getArrayItems(['a' => 'hello', 'b' => 'world'], ['a', 'c'], true)); } + + public function testArrayValueReplace() { + $this->assertEquals(array(), OnArray::arrayValueReplace(array(), '', '')); + $this->assertEquals(array(), OnArray::arrayValueReplace(array(), 'a', '')); + $this->assertEquals(array(), OnArray::arrayValueReplace(array(), '', 'b')); + $this->assertEquals(array(), OnArray::arrayValueReplace(array(), 'a', 'b')); + + $src = ['fruit1' => 'apple', 'fruit2' => 'cherry', 'fruit3' => 'banana']; + $this->assertEquals($src, OnArray::arrayValueReplace($src, '', '')); + $this->assertEquals($src, OnArray::arrayValueReplace($src, 'Z', '')); + $this->assertEquals($src, OnArray::arrayValueReplace($src, 'Z', 'Y')); + + $expected = ['fruit1' => 'Apple', 'fruit2' => 'cherry', 'fruit3' => 'bAnAnA']; + $this->assertEquals($expected, OnArray::arrayValueReplace($src, 'a', 'A')); + } } -- GitLab