/** * @var string */ private $formId = null; /** * @var Sip */ private $sip = null; /** * AbstractBuildForm constructor. * * @param array $formSpec * @param array $feSpecAction * @param array $feSpecNative */ public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative) { $this->formSpec = $formSpec; $this->feSpecAction = $feSpecAction; $this->feSpecNative = $feSpecNative; $this->store = Store::getInstance(); $this->db = new Database(); $this->evaluate = new Evaluate($this->store, $this->db); $this->showDebugInfo = ($this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes'); $this->sip = $this->store->getSipInstance(); // render mode specific $this->fillWrap(); $this->buildElementFunctionName = [ 'checkbox' => 'Checkbox', 'date' => 'DateTime', 'datetime' => 'DateTime', 'dateJQW' => 'DateJQW', 'datetimeJQW' => 'DateJQW', 'email' => 'Input', 'gridJQW' => 'GridJQW', 'hidden' => 'Hidden', 'text' => 'Input', 'time' => 'DateTime', 'note' => 'Note', 'password' => 'Input', 'radio' => 'Radio', 'select' => 'Select', 'subrecord' => 'Subrecord', 'upload' => 'File', 'fieldset' => 'Fieldset', 'pill' => 'Pill' ]; $this->buildRowName = [ 'checkbox' => 'Native', 'date' => 'Native', 'datetime' => 'Native', 'dateJQW' => 'Native', 'datetimeJQW' => 'Native', 'email' => 'Native', 'gridJQW' => 'Native', 'hidden' => 'Native', 'text' => 'Native', 'time' => 'Native', 'note' => 'Native', 'password' => 'Native', 'radio' => 'Native', 'select' => 'Native', 'subrecord' => 'Subrecord', 'upload' => 'Native', 'fieldset' => 'Fieldset', 'pill' => 'Pill' ]; $this->symbol[SYMBOL_EDIT] = ""; $this->symbol[SYMBOL_NEW] = ""; $this->symbol[SYMBOL_DELETE] = ""; $this->inputCheckPattern = Sanitize::inputCheckPatternArray(); } abstract public function fillWrap(); /** * Builds complete form. Depending of form specification, the layout will be 'plain' / 'table' / 'bootstrap'. * * @param $mode * @return string|array $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE: array of all formElement.dynamicUpdate-yes values/states * @throws CodeException * @throws DbException * @throws \qfq\UserFormException */ public function process($mode, $htmlElementNameIdZero = false) { $htmlHead = ''; $htmlTail = ''; $htmlSubrecords = ''; $htmlElements = ''; $json = array(); $modeCollectFe = FLAG_DYNAMIC_UPDATE; $storeUse = STORE_USE_DEFAULT; if ($mode === FORM_SAVE) { $modeCollectFe = FLAG_ALL; $storeUse = STORE_RECORD . STORE_TABLE_DEFAULT; } //
if ($mode === FORM_LOAD) { $htmlHead = $this->head(); $htmlTail = $this->tail(); $htmlSubrecords = $this->doSubrecords(); } $filter = $this->getProcessFilter(); if ($this->formSpec['multiMode'] !== 'none') { $parentRecords = $this->db->sql($this->formSpec['multiSql']); foreach ($parentRecords as $row) { $this->store->setVarArray($row, STORE_PARENT_RECORD, true); $jsonTmp = array(); $htmlElements = $this->elements($row['_id'], $filter, 0, $jsonTmp, $modeCollectFe); $json[] = $jsonTmp; } } else { $htmlElements = $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), $filter, 0, $json, $modeCollectFe, $htmlElementNameIdZero, $storeUse); } $htmlSip = $this->buildHiddenSip($json); //
return ($mode === FORM_LOAD) ? $htmlHead . $htmlElements . $htmlSip . $htmlTail . $htmlSubrecords : $json; } /** * Builds the head area of the form. * * @return string */ public function head() { $html = ''; $html .= '
formSpec['class'], TRUE) . '>'; // main
around everything // Logged in BE User will see a FormEdit Link $sipParamString = OnArray::toString($this->store->getStore(STORE_SIP), ':', ', ', "'"); $formEditUrl = $this->createFormEditUrl(); $html .= "

Edit [$sipParamString]

"; $html .= $this->wrapItem(WRAP_SETUP_TITLE, $this->formSpec['title'], true); $html .= $this->getFormTag(); return $html; } /** * If SHOW_DEBUG_INFO=yes: create a link (incl. SIP) to edit the current form. Show also the hidden content of the SIP. * * @return string String: Edit [sip:..., r:..., urlparam:..., ...] */ public function createFormEditUrl() { if (!$this->showDebugInfo) { return ''; } $queryStringArray = [ 'id' => $this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3), 'form' => 'form', 'r' => $this->formSpec['id'] ]; $queryString = Support::arrayToQueryString($queryStringArray); $sip = $this->store->getSipInstance(); $url = $sip->queryStringToSip($queryString); return $url; } /** * Wrap's $this->wrap[$item][WRAP_SETUP_START] around $value. If $flagOmitEmpty==true && $value=='': return ''. * * @param $item * @param $value * @param bool|false $flagOmitEmpty * @return string */ public function wrapItem($item, $value, $flagOmitEmpty = false) { if ($flagOmitEmpty && $value === "") return ''; return $this->wrap[$item][WRAP_SETUP_START] . $value . $this->wrap[$item][WRAP_SETUP_END]; } /** * Returns '
'-tag with various attributes. * * @return string */ public function getFormTag() { $attribute = $this->getFormTagAtrributes(); return ''; } /** * Build an assoc array with standard form attributes. * * @return array */ public function getFormTagAtrributes() { $attribute['id'] = $this->getFormId(); $attribute['method'] = 'post'; $attribute['action'] = $this->getActionUrl(); $attribute['target'] = '_top'; $attribute['accept-charset'] = 'UTF-8'; $attribute['autocomplete'] = 'on'; $attribute['enctype'] = $this->getEncType(); return $attribute; } /** * @return string */ public function getFormId() { if ($this->formId === null) { $this->formId = uniqid('qfq-form-'); } return $this->formId; } /** * Builds the HTML 'form'-tag inlcuding all attributes and target. * * Notice: the SIP will be transferred as POST Parameter. * * @return string * @throws DbException */ public function getActionUrl() { return API_DIR . '/save.php'; } /** * Determines the enctype. * * See: https://www.w3.org/wiki/HTML/Elements/form#HTML_Attributes * * @return string * @throws DbException */ public function getEncType() { $result = $this->db->sql("SELECT id FROM FormElement AS fe WHERE fe.formId=? AND fe.type='upload' LIMIT 1", ROW_REGULAR, [$this->formSpec['id']], 'Look for Formelement.type="upload"'); return (count($result) === 1) ? 'multipart/form-data' : 'application/x-www-form-urlencoded'; } abstract public function tail(); abstract public function doSubrecords(); abstract public function getProcessFilter(); /** * Process all FormElements: build corresponding HTML code. Collect and return all HTML code. * * @param $recordId * @param string $filter FORM_ELEMENTS_NATIVE | FORM_ELEMENTS_SUBRECORD | FORM_ELEMENTS_NATIVE_SUBRECORD * @param int $feIdContainer * @return string * @throws CodeException * @throws DbException * @throws \qfq\UserFormException */ public function elements($recordId, $filter = FORM_ELEMENTS_NATIVE, $feIdContainer = 0, &$json, $modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, $storeUse = STORE_USE_DEFAULT) { $html = ''; // get current data record if ($recordId > 0 && $this->store->getVar('id', STORE_RECORD) === false) { $row = $this->db->sql("SELECT * FROM " . $this->formSpec['tableName'] . " WHERE id = ?", ROW_EXPECT_1, array($recordId)); $this->store->setVarArray($row, STORE_RECORD); } // Iterate over all FormElements foreach ($this->feSpecNative as $fe) { if (($filter === FORM_ELEMENTS_NATIVE && $fe[FE_TYPE] === 'subrecord') || ($filter === FORM_ELEMENTS_SUBRECORD && $fe[FE_TYPE] !== 'subrecord') // || ($filter === FORM_ELEMENTS_DYNAMIC_UPDATE && $fe['dynamicUpdate'] === 'no') ) { continue; // skip this FE } $debugStack = array(); // Preparation for Log, Debug $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); // evaluate current FormElement $evaluate = new Evaluate($this->store, $this->db); $formElement = $evaluate->parseArray($fe, $debugStack); // Some Defaults $formElement = Support::setFeDefaults($formElement); Support::setIfNotSet($formElement, F_BS_LABEL_COLUMNS); Support::setIfNotSet($formElement, F_BS_INPUT_COLUMNS); Support::setIfNotSet($formElement, F_BS_NOTE_COLUMNS); $label = ($formElement[F_BS_LABEL_COLUMNS] == '') ? $this->formSpec[F_BS_LABEL_COLUMNS] : $formElement[F_BS_LABEL_COLUMNS]; $input = ($formElement[F_BS_INPUT_COLUMNS] == '') ? $this->formSpec[F_BS_INPUT_COLUMNS] : $formElement[F_BS_INPUT_COLUMNS]; $note = ($formElement[F_BS_NOTE_COLUMNS] == '') ? $this->formSpec[F_BS_NOTE_COLUMNS] : $formElement[F_BS_NOTE_COLUMNS]; $this->fillWrapLabelInputNote($label, $input, $note); // Get default value $value = ($formElement['value'] === '') ? $this->store->getVar($formElement['name'], $storeUse, $formElement['checkType']) : $formElement['value']; // Typically: $htmlElementNameIdZero = true // After Saving a record, staying on the form, the FormElements on the Client are still known as ':0'. $htmlFormElementId = HelperFormElement::buildFormElementId($formElement['name'], ($htmlElementNameIdZero) ? 0 : $recordId); // Construct Marshaller Name: buildElement $buildElementFunctionName = 'build' . $this->buildElementFunctionName[$formElement[FE_TYPE]]; $jsonElement = array(); // Render pure element $elementHtml = $this->$buildElementFunctionName($formElement, $htmlFormElementId, $value, $jsonElement); // $fake0 = $fe['dynamicUpdate']; // $fake1 = $formElement['dynamicUpdate']; // container elements do not have dynamicUpdate='yes'. Instead they deliver nested elements. if ($formElement['class'] == 'container') { if (count($jsonElement) > 0) { $json = array_merge($json, $jsonElement); } } else { // for non container elements: just add the current json status if ($modeCollectFe === FLAG_ALL || ($modeCollectFe == FLAG_DYNAMIC_UPDATE && $fe['dynamicUpdate'] == 'yes')) { $json[] = $jsonElement; } } // debugStack as Tooltip if ($this->showDebugInfo && count($debugStack) > 0) { // $elementHtml = Support::appendTooltip($elementHtml, implode("\n", OnArray::htmlentitiesOnArray($debugStack))); $elementHtml = Support::appendTooltip($elementHtml, implode("\n", $debugStack)); } // Construct Marshaller Name: buildRow $buildRowName = 'buildRow' . $this->buildRowName[$formElement[FE_TYPE]]; $html .= $this->$buildRowName($formElement, $elementHtml, $htmlFormElementId); // break; } // Log / Debug: Last FormElement has been processed. $this->store->setVar(SYSTEM_FORM_ELEMENT, '', STORE_SYSTEM); return $html; } abstract public function fillWrapLabelInputNote($label, $input, $note); /** * Create a hidden sip, based on latest STORE_SIP Values. Return complete HTML 'hidden' element. * * @param $json * @return string * @throws CodeException * @throws \qfq\UserFormException */ public function buildHiddenSip(&$json) { $sipArray = $this->store->getStore(STORE_SIP); unset($sipArray[SIP_SIP]); unset($sipArray[SIP_URLPARAM]); $queryString = Support::arrayToQueryString($sipArray); $sip = $this->store->getSipInstance(); $sipValue = $sip->queryStringToSip($queryString, RETURN_SIP); $json[] = $this->getJsonElementUpdate(CLIENT_SIP, $sipValue, FE_MODE_SHOW); return $this->buildNativeHidden(CLIENT_SIP, $sipValue); } /** * @param $htmlFormElementId * @param string|array $value * @param string $feMode disabled|readonly|'' * @return array */ private function getJsonElementUpdate($htmlFormElementId, $value, $feMode) { $json = $this->getJsonFeMode($feMode); $json['form-element'] = $htmlFormElementId; $json['value'] = $value; // $json['disabled'] = ($feMode === 'disabled'); // $json['readonly'] = ($feMode === 'readonly'); return $json; } /** * Set corresponding JSON attributes readonly/required/disabled, based on $formElement[FE_MODE]. * * @param array $feMode * @return array * @throws UserFormException */ private function getJsonFeMode($feMode) { $this->getFeMode($feMode, $hidden, $disabled, $required); return [API_JSON_HIDDEN => $hidden === 'yes', API_JSON_DISABLED => $disabled === 'yes', API_JSON_REQUIRED => $required === 'yes']; } /** * @param $feMode * @param $hidden * @param $disabled * @param $required * @throws \qfq\UserFormException */ private function getFeMode($feMode, &$hidden, &$disabled, &$required) { $hidden = 'no'; $disabled = 'no'; $required = 'no'; switch ($feMode) { case FE_MODE_SHOW: break; case FE_MODE_REQUIRED: $required = 'yes'; break; case FE_MODE_READONLY: $disabled = 'yes'; // convert the UI status 'readonly' to the HTML/CSS status 'disabled'. break; case FE_MODE_HIDDEN: $hidden = 'yes'; break; default: throw new UserFormException("Unknown mode '$feMode'", ERROR_UNKNOWN_MODE); break; } } /** * Builds a real HTML hidden form element. Useful for checkboxes, Multiple-Select and Radios. * * @param $htmlFormElementId * @param $value * @return string */ public function buildNativeHidden($htmlFormElementId, $value) { return ''; } /** * Takes the current SIP ('form' and additional parameter), set SIP_RECORD_ID=0 and create a new 'NewRecordUrl'. * * @throws CodeException * @throws \qfq\UserFormException */ public function deriveNewRecordUrlFromExistingSip(&$toolTipNew) { $urlParam = $this->store->getStore(STORE_SIP); $urlParam[SIP_RECORD_ID] = 0; unset($urlParam[SIP_SIP]); unset($urlParam[SIP_URLPARAM]); Support::appendTypo3ParameterToArray($urlParam); $sip = $this->store->getSipInstance(); $url = $sip->queryStringToSip(OnArray::toString($urlParam)); if ($this->showDebugInfo) { //TODO: missing decoding of SIP $toolTipNew .= PHP_EOL . PHP_EOL . OnArray::toString($urlParam, ' = ', PHP_EOL, "'"); } return $url; } abstract public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId); abstract public function buildRowPill(array $formElement, $elementHtml); abstract public function buildRowFieldset(array $formElement, $elementHtml); abstract public function buildRowSubrecord(array $formElement, $elementHtml); /** * Builds a label, typically for an html-''-element. * * @param string $htmlFormElementId * @param string $label * @return string */ public function buildLabel($htmlFormElementId, $label) { $attributes = Support::doAttribute('for', $htmlFormElementId); $attributes .= Support::doAttribute('class', 'control-label'); $html = Support::wrapTag("