/** * @var Store */ protected $store = null; /** * @var Evaluate */ protected $evaluate = null; /** * @var string */ private $formId = null; /** * @var Sip */ private $sip = null; /** * @var Link */ protected $link = null; /** * @var Report */ private $report = null; /** * @var BodytextParser */ private $bodytextParser = null; /** * @var Database[] - Array of Database instantiated class */ protected $dbArray = array(); /** * @var bool|mixed */ protected $dbIndexData = false; /** * @var bool|string */ protected $dbIndexQfq = false; /** * AbstractBuildForm constructor. * * @param array $formSpec * @param array $feSpecAction * @param array $feSpecNative * @param array Database $db * @throws \CodeException * @throws \UserFormException * @throws \UserReportException */ public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $db = null) { $this->formSpec = $formSpec; $this->feSpecAction = $feSpecAction; $this->feSpecNative = $feSpecNative; $this->store = Store::getInstance(); // $this->dbIndexData = $this->store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM); $this->dbIndexData = $formSpec[F_DB_INDEX]; $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM); $this->dbArray = $db; $this->evaluate = new Evaluate($this->store, $this->dbArray[$this->dbIndexData]); $this->showDebugInfoFlag = Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM)); $this->sip = $this->store->getSipInstance(); $this->link = new Link($this->sip, $this->dbIndexData); // render mode specific $this->fillWrap(); $this->buildElementFunctionName = [ FE_TYPE_CHECKBOX => 'Checkbox', FE_TYPE_DATE => 'DateTime', FE_TYPE_DATETIME => 'DateTime', 'dateJQW' => 'DateJQW', 'datetimeJQW' => 'DateJQW', 'email' => 'Input', 'gridJQW' => 'GridJQW', FE_TYPE_EXTRA => 'Extra', FE_TYPE_TEXT => 'Input', FE_TYPE_EDITOR => 'Editor', FE_TYPE_TIME => 'DateTime', FE_TYPE_NOTE => 'Note', FE_TYPE_PASSWORD => 'Input', FE_TYPE_RADIO => 'Radio', FE_TYPE_SELECT => 'Select', FE_TYPE_SUBRECORD => 'Subrecord', FE_TYPE_UPLOAD => 'File', FE_TYPE_ANNOTATE => 'Annotate', FE_TYPE_IMAGE_CUT => 'ImageCut', 'fieldset' => 'Fieldset', 'pill' => 'Pill', 'templateGroup' => 'TemplateGroup', ]; $this->buildRowName = [ FE_TYPE_CHECKBOX => 'Native', FE_TYPE_DATE => 'Native', FE_TYPE_DATETIME => 'Native', 'dateJQW' => 'Native', 'datetimeJQW' => 'Native', 'email' => 'Native', 'gridJQW' => 'Native', FE_TYPE_EXTRA => 'Native', FE_TYPE_TEXT => 'Native', FE_TYPE_EDITOR => 'Native', FE_TYPE_TIME => 'Native', FE_TYPE_NOTE => 'Native', FE_TYPE_PASSWORD => 'Native', FE_TYPE_RADIO => 'Native', FE_TYPE_SELECT => 'Native', FE_TYPE_SUBRECORD => 'Subrecord', FE_TYPE_UPLOAD => 'Native', FE_TYPE_ANNOTATE => 'Native', FE_TYPE_IMAGE_CUT => 'Native', 'fieldset' => 'Fieldset', 'pill' => 'Pill', 'templateGroup' => 'TemplateGroup', ]; $this->symbol[SYMBOL_EDIT] = ""; $this->symbol[SYMBOL_SHOW] = ""; $this->symbol[SYMBOL_NEW] = ""; $this->symbol[SYMBOL_DELETE] = ""; } abstract public function fillWrap(); /** * @param $filter * @param $modeCollectFe * @param array $rcJson * @return string * @throws \CodeException * @throws \DbException * @throws \DownloadException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \Twig\Error\LoaderError * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError * @throws \UserFormException * @throws \UserReportException */ private function buildMultiForm($filter, $modeCollectFe, array &$rcJson) { $htmlElements = ''; $rcJson = array(); $parentRecords = $this->evaluate->parse($this->formSpec[F_MULTI_SQL], ROW_REGULAR); // No rows: nothing to do. if (empty($parentRecords)) { return $this->formSpec[F_MULTI_MSG_NO_RECORD]; } // Check for 'id' or '_id' as column name $idName = isset($parentRecords[0]['_' . F_MULTI_COL_ID]) ? '_' . F_MULTI_COL_ID : F_MULTI_COL_ID; // Check that an column 'id' is given if (!isset($parentRecords[0][$idName])) { throw new \UserFormException( json_encode([ERROR_MESSAGE_TO_USER => 'Missing column "_' . F_MULTI_COL_ID . '"', ERROR_MESSAGE_TO_DEVELOPER => $this->formSpec[F_MULTI_SQL]]), ERROR_INVALID_OR_MISSING_PARAMETER); } // This is a dirty workaround for formSave: clear FORM STORE (already outdated values). // Otherwise those outdated values will be taken to fill non primary FE in multiForm (which are garbage). // Better solution would be to have FORM_STORE in sync. $this->store::unsetStore(STORE_FORM); $storeVarBase = $this->evaluate->parse($this->formSpec[FE_FILL_STORE_VAR]); if (!is_array($storeVarBase)) { $storeVarBase = array(); } // Per row, iterate over all form elements foreach ($parentRecords as $row) { // Always start with a clean STORE_VAR $this->store->setStore($storeVarBase, STORE_VAR, true); $this->store->setStore($row, STORE_PARENT_RECORD, true); $this->store->setVar(F_MULTI_COL_ID, $row[$idName], STORE_PARENT_RECORD); // In case '_id' is used, both '_id' and 'id' should be accessible. $record = $this->dbArray[$this->dbIndexData]->sql('SELECT * FROM `' . $this->formSpec[F_TABLE_NAME] . '` WHERE `id`=' . $row[F_MULTI_COL_ID], ROW_EXPECT_1); $this->store->setStore($record, STORE_RECORD, true); $jsonTmp = array(); $feTmp = $this->feSpecNative; $leftColumns = $this->buildMultiFormLeftColumns($row); $rightInputs = $this->elements($row[$idName], $filter, 0, $jsonTmp, $modeCollectFe, false, STORE_USE_DEFAULT, FORM_LOAD, true); $htmlElements .= Support::wrapTag('', $leftColumns . $rightInputs); // Clean for the next round $this->feSpecNative = $feTmp; $this->store::unsetStore(STORE_RECORD); $rcJson = array_merge($rcJson, $jsonTmp); } $tableHead = Support::wrapTag('', $this->buildMultiFormTableHead($parentRecords[0])); return '' . $tableHead . '' . $htmlElements . '
'; } /** * @param array $row * @return string */ private function buildMultiFormLeftColumns(array $row) { $line = ''; // Collect columns foreach ($row as $key => $value) { if (($key[0] ?? '') != '_') { $line .= "$value"; } } return $line; } /** * @param array $row * @return string * @throws \CodeException * @throws \UserFormException */ private function buildMultiFormTableHead(array $row) { $line = ''; // Collect columns foreach ($row as $key => $value) { if (($key[0] ?? '') != '_') { $line .= "$key"; } } // Collect label from FormElements foreach ($this->feSpecNative as $formElement) { $editFeHtml = ''; // debugStack as Tooltip if ($this->showDebugInfoFlag) { // Build 'FormElement' Edit symbol $feEditUrl = $this->createFormEditorUrl(FORM_NAME_FORM_ELEMENT, $formElement[FE_ID], ['formId' => $formElement[FE_FORM_ID]]); $titleAttr = Support::doAttribute('title', $this->formSpec[FE_NAME] . ' / ' . $formElement[FE_NAME] . ' [' . $formElement[FE_ID] . ']'); $icon = Support::wrapTag('', ''); $editFeHtml = ' ' . Support::wrapTag("

Edit [$sipParamString]

"; $html .= $this->wrapItem(WRAP_SETUP_TITLE, $this->formSpec[F_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. * * @param string $form FORM_NAME_FORM | FORM_NAME_FORM_ELEMENT * @param int $recordId id of form or formElement * @param array $param * * @return string String: Edit [sip:..., r:..., urlparam:..., * ...] * @throws \CodeException * @throws \UserFormException */ public function createFormEditorUrl($form, $recordId, array $param = array()) { if (!$this->showDebugInfoFlag) { return ''; } $queryStringArray = [ 'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM), 'form' => $form, 'r' => $recordId, PARAM_DB_INDEX_DATA => $this->dbIndexQfq, ]; $queryStringArray = array_merge($queryStringArray, $param); $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 string $item * @param string $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 * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function getFormTag() { $md5 = ''; $attribute = $this->getFormTagAttributes(); $honeypot = $this->getHoneypotVars(); $md5 = $this->buildInputRecordHashMd5(); return '' . $honeypot . $md5; } /** * Build MD5 from the current record. Return HTML Input element. * * @return string * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function buildInputRecordHashMd5() { $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO); $md5 = $this->buildRecordHashMd5($this->formSpec[F_TABLE_NAME], $recordId, $this->formSpec[F_PRIMARY_KEY]); $data = ""; // $data = ""; return $data; } /** * @param $tableName * @param $recordId * @param string $primaryKey * * @return string * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function buildRecordHashMd5($tableName, $recordId, $primaryKey = F_PRIMARY_KEY_DEFAULT) { $record = array(); if ($recordId != 0) { $record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_1, [$recordId], "Record to load not found. " . (FEATURE_FORM_FILE_SYNC ? FormAsFile::errorHintFormImport($tableName) : '')); } if (isset($record[F_FILE_STATS])) { // why: The column "fileStats" in the Form table is modified when a form is exported to a file but nothing else changes. unset($record[F_FILE_STATS]); } return OnArray::getMd5($record); } /** * Create HTML Input vars to detect bot automatic filling of forms. * * @return string * @throws \CodeException * @throws \UserFormException */ public function getHoneypotVars() { $html = ''; $vars = $this->store->getVar(SYSTEM_SECURITY_VARS_HONEYPOT, STORE_SYSTEM); // Iterate over all fake vars $arr = explode(',', $vars); foreach ($arr as $name) { $name = trim($name); if ($name === '') { continue; } $html .= ""; } return $html; } /** * Build an assoc array with standard form attributes. * * @return array * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function getFormTagAttributes() { $attribute['id'] = $this->getFormId(); $attribute['method'] = 'post'; $attribute['action'] = $this->getActionUrl(); $attribute['target'] = '_top'; $attribute['accept-charset'] = 'UTF-8'; $attribute[FE_INPUT_AUTOCOMPLETE] = 'on'; $attribute['enctype'] = $this->getEncType(); $attribute['data-disable-return-key-submit'] = $this->formSpec[F_ENTER_AS_SUBMIT] == '1' ? "false" : "true"; // attribute meaning is inverted $attribute['data-activate-first-required-tab'] = $this->formSpec[F_ACTIVATE_FIRST_REQUIRED_TAB] == '1' ? "true" : "false"; // attribute meaning is inverted return $attribute; } /** * Return a uniq form id * * @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 */ public function getActionUrl() { return Path::urlApi(API_SAVE_PHP); } /** * Determines the enctype. * * See: https://www.w3.org/wiki/HTML/Elements/form#HTML_Attributes * * @return string * @throws \CodeException * @throws \DbException * @throws \UserFormException */ public function getEncType() { $result = $this->dbArray[$this->dbIndexQfq]->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'; } /** * @return mixed */ abstract public function getProcessFilter(); /** * @param array|string $value * * @return array|string * @throws \CodeException * @throws \DbException * @throws \DownloadException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \Twig\Error\LoaderError * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError * @throws \UserFormException * @throws \UserReportException */ private function processReportSyntax($value) { if (is_array($value)) { $new = array(); //might happen for e.g Template Groups foreach ($value as $item) { $new[] = $this->processReportSyntax($item); } return $new; } $value = trim($value); if (substr($value, 0, 8) == SHEBANG_REPORT) { if ($this->report === null) { $this->report = new Report(array(), $this->evaluate, false); } if ($this->bodytextParser === null) { $this->bodytextParser = new BodytextParser(); } $storeRecord = $this->store->getStore(STORE_RECORD); $value = $this->report->process($this->bodytextParser->process($value)); $this->store->setStore($storeRecord, STORE_RECORD, true); $this->store->setVar(SYSTEM_REPORT_FULL_LEVEL, '', STORE_SYSTEM); // debug } return $value; } /** * Process all FormElements in $this->feSpecNative: Collect and return all HTML code & JSON. * * @param int $recordId * @param string $filter FORM_ELEMENTS_NATIVE | FORM_ELEMENTS_SUBRECORD | FORM_ELEMENTS_NATIVE_SUBRECORD * @param int $feIdContainer * @param array $json * @param string $modeCollectFe * @param bool $htmlElementNameIdZero * @param string $storeUseDefault * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE * @param bool $flagMulti * * @return string * @throws \CodeException * @throws \DbException * @throws \DownloadException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \Twig\Error\LoaderError * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError * @throws \UserFormException * @throws \UserReportException */ public function elements($recordId, $filter, $feIdContainer, array &$json, $modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false, $storeUseDefault = STORE_USE_DEFAULT, $mode = FORM_LOAD, $flagMulti = false) { $html = ''; // The following 'FormElement.parameter' will never be used during load (fe.type='upload'). FE_PARAMETER has been already expanded. $skip = [FE_SQL_UPDATE, FE_SQL_INSERT, FE_SQL_DELETE, FE_SQL_AFTER, FE_SQL_BEFORE, FE_PARAMETER, FE_FILL_STORE_VAR, FE_FILE_DOWNLOAD_BUTTON, FE_TYPEAHEAD_GLUE_INSERT, FE_TYPEAHEAD_GLUE_DELETE, FE_TYPEAHEAD_TAG_INSERT, FE_TYPEAHEAD_INITIAL_SUGGESTION]; // get current data record $primaryKey = $this->formSpec[F_PRIMARY_KEY]; if ($recordId > 0 && $this->store->getVar($primaryKey, STORE_RECORD) === false) { $tableName = $this->formSpec[F_TABLE_NAME]; $row = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey` = ?", ROW_EXPECT_1, array($recordId), "Form '" . $this->formSpec[F_NAME] . "' failed to load record '$primaryKey'='$recordId' from table '" . $this->formSpec[F_TABLE_NAME] . "'."); $this->store->setStore($row, STORE_RECORD); } $this->checkAutoFocus(); $parameterLanguageFieldName = $this->store->getVar(SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME, STORE_SYSTEM); // Iterate over all FormElements foreach ($this->feSpecNative as $fe) { $storeUse = $storeUseDefault; if (($filter === FORM_ELEMENTS_NATIVE && $fe[FE_TYPE] === 'subrecord') || ($filter === FORM_ELEMENTS_SUBRECORD && $fe[FE_TYPE] !== 'subrecord') // || ($filter === FORM_ELEMENTS_DYNAMIC_UPDATE && $fe[FE_DYNAMIC_UPDATE] === 'no') ) { continue; // skip this FE } // #7705. Dirty fix: 'form_save and fresh created tg element can't be updated, cause the HTML id is unknown: skip those. if ($mode == FORM_SAVE && false != stripos($fe[FE_NAME], '%d')) { continue; // skip this FE } $flagOutput = ($fe[FE_TYPE] !== FE_TYPE_EXTRA); // type='extra' will not displayed and not transmitted to the form. $debugStack = array(); // Preparation for Log, Debug $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM); $this->store->setVar(SYSTEM_FORM_ELEMENT_ID, $fe[FE_ID], STORE_SYSTEM); // Fill STORE_LDAP $fe = $this->prepareFillStoreFireLdap($fe); if (isset($fe[FE_FILL_STORE_VAR])) { $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_FILL_STORE_VAR, STORE_SYSTEM); // debug $fe[FE_FILL_STORE_VAR] = $this->evaluate->parse($fe[FE_FILL_STORE_VAR], ROW_EXPECT_0_1); $this->store->appendToStore($fe[FE_FILL_STORE_VAR], STORE_VAR); } // If given, slaveId will be copied to STORE_VAR if (!empty($fe[FE_SLAVE_ID])) { $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_SLAVE_ID, STORE_SYSTEM); // debug $slaveId = Support::falseEmptyToZero($this->evaluate->parse($fe[FE_SLAVE_ID])); $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR); } $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_VALUE, STORE_SYSTEM); // debug $fe[FE_VALUE] = $this->processReportSyntax($fe[FE_VALUE]); $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, FE_NOTE, STORE_SYSTEM); // debug $fe[FE_NOTE] = $this->processReportSyntax($fe[FE_NOTE]); // ** evaluate current FormElement ** $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, 'Some of the columns of current FormElement', STORE_SYSTEM); // debug $formElement = $this->evaluate->parseArray($fe, $skip, $debugStack); $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, 'Set language', STORE_SYSTEM); // debug $formElement = HelperFormElement::setLanguage($formElement, $parameterLanguageFieldName); // Some Defaults $formElement = Support::setFeDefaults($formElement, $this->formSpec); // // Copy global readonly mode. // if ($this->formSpec[F_MODE] == F_MODE_READONLY) { // $fe[FE_MODE] = FE_MODE_READONLY; // $fe[FE_MODE_SQL] = ''; // } if ($flagOutput === true && !$flagMulti) { $this->fillWrapLabelInputNote($formElement[FE_BS_LABEL_COLUMNS], $formElement[FE_BS_INPUT_COLUMNS], $formElement[FE_BS_NOTE_COLUMNS]); } Support::setIfNotSet($formElement, FE_VALUE); if (is_array($formElement[FE_VALUE])) { $formElement[FE_VALUE] = (count($formElement[FE_VALUE])) > 0 ? current($formElement[FE_VALUE][0]) : ''; } $value = $formElement[FE_VALUE]; if ($value === '') { // #2064 / Only take the default, if the FE is a real tablecolumn. // #3426 / Dynamic Update: Inputs loose the new content and shows the old value. if ($storeUse == STORE_USE_DEFAULT && $this->store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES) === false) { $storeUse = str_replace(STORE_TABLE_DEFAULT, '', $storeUse); // Remove STORE_DEFAULT } // In case the current element is a 'RETYPE' element: take the element name of the source FormElement. Needed to retrieve the default value later. $name = (isset($formElement[FE_RETYPE_SOURCE_NAME])) ? $formElement[FE_RETYPE_SOURCE_NAME] : $formElement[FE_NAME]; // Retrieve value via FSRVD $sanitizeClass = ($mode == FORM_UPDATE) ? SANITIZE_ALLOW_ALL : $formElement[FE_CHECK_TYPE]; $value = $this->store->getVar($name, $storeUse, $sanitizeClass, $foundInStore); // For typeAhead fields: perform prefetch to display description instead of key (#5444) if ($mode == FORM_SAVE && isset($fe[FE_TYPEAHEAD_SQL_PREFETCH])) { $config = [FE_TYPEAHEAD_SQL_PREFETCH => $fe[FE_TYPEAHEAD_SQL_PREFETCH]]; $value = TypeAhead::typeAheadSqlPrefetch($config, $value, $this->dbArray[$this->dbIndexData]); } } if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) { // $value = htmlspecialchars_decode($value, ENT_QUOTES); $value = Support::htmlEntityEncodeDecode(MODE_DECODE, $value); } // Typically: $htmlElementNameIdZero = true // After Saving a record, staying on the form, the FormElements on the Client are still known as ':0'. $htmlFormElementName = HelperFormElement::buildFormElementName($formElement, ($htmlElementNameIdZero) ? 0 : $recordId); $formElement[FE_HTML_ID] = HelperFormElement::buildFormElementId($this->formSpec[F_ID], $formElement[FE_ID], ($htmlElementNameIdZero) ? 0 : $recordId, $formElement[FE_TG_INDEX]); // Construct Marshaller Name: buildElement $buildElementFunctionName = 'build' . $this->buildElementFunctionName[$formElement[FE_TYPE]]; $jsonElement = array(); $elementExtra = ''; // Render pure element $elementHtml = $this->$buildElementFunctionName($formElement, $htmlFormElementName, $value, $jsonElement, $mode); // container elements do not have dynamicUpdate='yes'. Instead they deliver nested elements. if ($formElement[FE_CLASS] == FE_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[FE_DYNAMIC_UPDATE] === 'yes')) { if (isset($jsonElement[0]) && is_array($jsonElement[0])) { // Checkboxes are delivered as array of arrays: unnest them and append them to the existing json array. $json = array_merge($json, $jsonElement); } else { $json[] = $jsonElement; } } } if ($flagOutput) { // debugStack as Tooltip if ($this->showDebugInfoFlag) { if (count($debugStack) > 0) { $elementHtml .= Support::doTooltip($formElement[FE_HTML_ID] . HTML_ID_EXTENSION_TOOLTIP, implode("\n", $debugStack)); } if (!$flagMulti) { // Build 'FormElement' Edit symbol. MultiForms: Edit symbol is in thead. $feEditUrl = $this->createFormEditorUrl(FORM_NAME_FORM_ELEMENT, $formElement[FE_ID], ['formId' => $formElement[FE_FORM_ID]]); $titleAttr = Support::doAttribute('title', $this->formSpec[FE_NAME] . ' / ' . $formElement[FE_NAME] . ' [' . $formElement[FE_ID] . ']'); $icon = Support::wrapTag('', ''); $elementHtml .= Support::wrapTag("