/** * Render tree widget * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $table = $this->globalOptions['table']; $field = $this->globalOptions['fieldName']; $row = $this->globalOptions['databaseRow']; $parameterArray = $this->globalOptions['parameterArray']; // Field configuration from TCA: $config = $parameterArray['fieldConf']['config']; $disabled = ''; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = ' disabled="disabled"'; } $resultArray = $this->initializeResultArray(); // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist. $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']); $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray); $html = $this->renderField($table, $field, $row, $parameterArray, $config, $selItems); // Wizards: if (!$disabled) { $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />'; $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf); } $resultArray['html'] = $html; return $resultArray; }
/** * @test */ public function databaseRowCompatibilityImplodesSelectArrayWithValuesAtSecondPosition() { $input = ['uid' => 42, 'simpleArray' => [0 => [0 => 'aLabel', 1 => 'aValue'], 1 => [0 => 'anotherLabel', 1 => 'anotherValue']]]; $expected = $input; $expected['simpleArray'] = 'aValue,anotherValue'; $this->assertEquals($expected, FormEngineUtility::databaseRowCompatibility($input)); }
/** * This will render a series of radio buttons. * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $parameterArray = $this->globalOptions['parameterArray']; $config = $parameterArray['fieldConf']['config']; $html = ''; $disabled = ''; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = ' disabled'; } // Get items for the array $selectedItems = FormEngineUtility::initItemArray($parameterArray['fieldConf']); if ($config['itemsProcFunc']) { $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class); $selectedItems = $dataPreprocessor->procItems($selectedItems, $parameterArray['fieldTSConfig']['itemsProcFunc.'], $config, $this->globalOptions['table'], $this->globalOptions['databaseRow'], $this->globalOptions['fieldName']); } // Traverse the items, making the form elements foreach ($selectedItems as $radioButton => $selectedItem) { if (isset($parameterArray['fieldTSConfig']['altLabels.'][$radioButton])) { $label = $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['altLabels.'][$radioButton]); } else { $label = $selectedItem[0]; } $radioId = htmlspecialchars($parameterArray['itemFormElID'] . '_' . $radioButton); $radioOnClick = implode('', $parameterArray['fieldChangeFunc']); $radioChecked = (string) $selectedItem[1] === (string) $parameterArray['itemFormElValue'] ? ' checked="checked"' : ''; $html .= '<div class="radio' . $disabled . '">' . '<label for="' . $radioId . '">' . '<input ' . 'type="radio" ' . 'name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" ' . 'id="' . $radioId . '" ' . 'value="' . htmlspecialchars($selectedItem[1]) . '" ' . $radioChecked . ' ' . $parameterArray['onFocus'] . ' ' . $disabled . ' ' . 'onclick="' . htmlspecialchars($radioOnClick) . '" ' . '/>' . htmlspecialchars($label) . '</label>' . '</div>'; } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; return $resultArray; }
/** * Calculate and return the current type value of a record * * @param string $table The table name. MUST be in $GLOBALS['TCA'] * @param array $row The row from the table, should contain at least the "type" field, if applicable. * @return string Return the "type" value for this record, ready to pick a "types" configuration from the $GLOBALS['TCA'] array. * @throws \RuntimeException */ protected function getRecordTypeValue($table, array $row) { $typeNum = 0; $field = $GLOBALS['TCA'][$table]['ctrl']['type']; if ($field) { if (strpos($field, ':') !== FALSE) { list($pointerField, $foreignTypeField) = explode(':', $field); $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config']; $relationType = $fieldConfig['type']; if ($relationType === 'select') { $foreignUid = $row[$pointerField]; $foreignTable = $fieldConfig['foreign_table']; } elseif ($relationType === 'group') { $values = FormEngineUtility::extractValuesOnlyFromValueLabelList($row[$pointerField]); list(, $foreignUid) = GeneralUtility::revExplode('_', $values[0], 2); $allowedTables = explode(',', $fieldConfig['allowed']); // Always take the first configured table. $foreignTable = $allowedTables[0]; } else { throw new \RuntimeException('TCA Foreign field pointer fields are only allowed to be used with group or select field types.', 1325861239); } if ($foreignUid) { $foreignRow = BackendUtility::getRecord($foreignTable, $foreignUid, $foreignTypeField); $this->registerDefaultLanguageData($foreignTable, $foreignRow); if ($foreignRow[$foreignTypeField]) { $foreignTypeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]; $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($foreignTable, $foreignRow, $foreignTypeField, $foreignTypeFieldConfig); } } } else { $typeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]; $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($table, $row, $field, $typeFieldConfig); } } if (empty($typeNum)) { // If that value is an empty string, set it to "0" (zero) $typeNum = 0; } // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist) if (!$GLOBALS['TCA'][$table]['types'][$typeNum]) { $typeNum = $GLOBALS['TCA'][$table]['types']['0'] ? 0 : 1; } // Force to string. Necessary for eg '-1' to be recognized as a type value. return (string)$typeNum; }
/** * Convert the DOM object-id of an inline container to an array. * The object-id could look like 'data-parentPageId-tx_mmftest_company-1-employees'. * This initializes $this->inlineStructure - used by AJAX entry points * There are two keys: * - 'stable': Containing full qualified identifiers (table, uid and field) * - 'unstable': Containing partly filled data (e.g. only table and possibly field) * * @param string $domObjectId The DOM object-id * @param bool $loadConfig Load the TCA configuration for that level (default: TRUE) * @return void */ public function initializeByParsingDomObjectIdString($domObjectId, $loadConfig = TRUE) { $unstable = array(); $vector = array('table', 'uid', 'field'); // Substitute FlexForm addition and make parsing a bit easier $domObjectId = str_replace('---', ':', $domObjectId); // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>) $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/'; if (preg_match($pattern, $domObjectId, $match)) { $inlineFirstPid = $match[1]; $parts = explode('-', $match[2]); $partsCnt = count($parts); for ($i = 0; $i < $partsCnt; $i++) { if ($i > 0 && $i % 3 == 0) { // Load the TCA configuration of the table field and store it in the stack if ($loadConfig) { $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config']; // Fetch TSconfig: $TSconfig = FormEngineUtility::getTSconfigForTableRow($unstable['table'], array('uid' => $unstable['uid'], 'pid' => $inlineFirstPid), $unstable['field']); // Override TCA field config by TSconfig: if (!$TSconfig['disabled']) { $unstable['config'] = FormEngineUtility::overrideFieldConf($unstable['config'], $TSconfig); } $unstable['localizationMode'] = BackendUtility::getInlineLocalizationMode($unstable['table'], $unstable['config']); } // Extract FlexForm from field part (if any) if (strpos($unstable['field'], ':') !== FALSE) { $fieldParts = GeneralUtility::trimExplode(':', $unstable['field']); $unstable['field'] = array_shift($fieldParts); // FlexForm parts start with data: if (count($fieldParts) > 0 && $fieldParts[0] === 'data') { $unstable['flexform'] = $fieldParts; } } $this->inlineStructure['stable'][] = $unstable; $unstable = array(); } $unstable[$vector[$i % 3]] = $parts[$i]; } if (count($unstable)) { $this->inlineStructure['unstable'] = $unstable; } } }
/** * Entry method * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $table = $this->globalOptions['table']; $row = $this->globalOptions['databaseRow']; $fieldName = $this->globalOptions['fieldName']; $flexFormDataStructureArray = $this->globalOptions['flexFormDataStructureArray']; $flexFormRowData = $this->globalOptions['flexFormRowData']; $flexFormCurrentLanguage = $this->globalOptions['flexFormCurrentLanguage']; $flexFormNoEditDefaultLanguage = $this->globalOptions['flexFormNoEditDefaultLanguage']; $flexFormFormPrefix = $this->globalOptions['flexFormFormPrefix']; $parameterArray = $this->globalOptions['parameterArray']; $languageService = $this->getLanguageService(); $resultArray = $this->initializeResultArray(); foreach ($flexFormDataStructureArray as $flexFormFieldName => $flexFormFieldArray) { if (!is_array($flexFormFieldArray) || !isset($flexFormFieldArray['type']) && !is_array($flexFormFieldArray['TCEforms']['config'])) { continue; } if ($flexFormFieldArray['type'] === 'array') { // Section if (empty($flexFormFieldArray['section'])) { $resultArray['html'] = LF . 'Section expected at ' . $flexFormFieldName . ' but not found'; continue; } $sectionTitle = ''; if (!empty($flexFormFieldArray['title'])) { $sectionTitle = $languageService->sL($flexFormFieldArray['title']); } $options = $this->globalOptions; $options['flexFormDataStructureArray'] = $flexFormFieldArray['el']; $options['flexFormRowData'] = is_array($flexFormRowData[$flexFormFieldName]['el']) ? $flexFormRowData[$flexFormFieldName]['el'] : array(); $options['flexFormSectionType'] = $flexFormFieldName; $options['flexFormSectionTitle'] = $sectionTitle; $options['renderType'] = 'flexFormSectionContainer'; /** @var NodeFactory $nodeFactory */ $nodeFactory = $this->globalOptions['nodeFactory']; $sectionContainerResult = $nodeFactory->create($options)->render(); $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $sectionContainerResult); } else { // Single element $vDEFkey = 'vDEF'; $displayConditionResult = TRUE; if (!empty($flexFormFieldArray['TCEforms']['displayCond'])) { $conditionData = is_array($flexFormRowData) ? $flexFormRowData : array(); $conditionData['parentRec'] = $row; /** @var $elementConditionMatcher ElementConditionMatcher */ $elementConditionMatcher = GeneralUtility::makeInstance(ElementConditionMatcher::class); $displayConditionResult = $elementConditionMatcher->match($flexFormFieldArray['TCEforms']['displayCond'], $conditionData, $vDEFkey); } if (!$displayConditionResult) { continue; } // On-the-fly migration for flex form "TCA" // @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8. This can be removed *if* no additional TCA migration is added with CMS 8, see class TcaMigration $dummyTca = array('dummyTable' => array('columns' => array('dummyField' => $flexFormFieldArray['TCEforms']))); $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class); $migratedTca = $tcaMigration->migrate($dummyTca); $messages = $tcaMigration->getMessages(); if (!empty($messages)) { $context = 'FormEngine did an on-the-fly migration of a flex form data structure. This is deprecated and will be removed' . ' with TYPO3 CMS 8. Merge the following changes into the flex form definition of table ' . $table . ' in field ' . $fieldName . ':'; array_unshift($messages, $context); GeneralUtility::deprecationLog(implode(LF, $messages)); } $flexFormFieldArray['TCEforms'] = $migratedTca['dummyTable']['columns']['dummyField']; // Set up options for single element $fakeParameterArray = array('fieldConf' => array('label' => $languageService->sL(trim($flexFormFieldArray['TCEforms']['label'])), 'config' => $flexFormFieldArray['TCEforms']['config'], 'defaultExtras' => $flexFormFieldArray['TCEforms']['defaultExtras'], 'onChange' => $flexFormFieldArray['TCEforms']['onChange'])); // Force a none field if default language can not be edited if ($flexFormNoEditDefaultLanguage && $flexFormCurrentLanguage === 'lDEF') { $fakeParameterArray['fieldConf']['config'] = array('type' => 'none', 'rows' => 2); } $alertMsgOnChange = ''; if ($fakeParameterArray['fieldConf']['onChange'] === 'reload' || !empty($GLOBALS['TCA'][$table]['ctrl']['type']) && $GLOBALS['TCA'][$table]['ctrl']['type'] === $flexFormFieldName || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate']) && GeneralUtility::inList($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'], $flexFormFieldName)) { if ($this->getBackendUserAuthentication()->jsConfirmation(JsConfirmation::TYPE_CHANGE)) { $alertMsgOnChange = 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };'; } else { $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm();}'; } } $fakeParameterArray['fieldChangeFunc'] = $parameterArray['fieldChangeFunc']; if ($alertMsgOnChange) { $fakeParameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange; } $fakeParameterArray['onFocus'] = $parameterArray['onFocus']; $fakeParameterArray['label'] = $parameterArray['label']; $fakeParameterArray['itemFormElName'] = $parameterArray['itemFormElName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $vDEFkey . ']'; $fakeParameterArray['itemFormElID'] = $fakeParameterArray['itemFormElName']; if (isset($flexFormRowData[$flexFormFieldName][$vDEFkey])) { $fakeParameterArray['itemFormElValue'] = $flexFormRowData[$flexFormFieldName][$vDEFkey]; } else { $fakeParameterArray['itemFormElValue'] = $fakeParameterArray['fieldConf']['config']['default']; } $options = $this->globalOptions; $options['parameterArray'] = $fakeParameterArray; $options['elementBaseName'] = $this->globalOptions['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $vDEFkey . ']'; if (!empty($flexFormFieldArray['TCEforms']['config']['renderType'])) { $options['renderType'] = $flexFormFieldArray['TCEforms']['config']['renderType']; } else { // Fallback to type if no renderType is given $options['renderType'] = $flexFormFieldArray['TCEforms']['config']['type']; } /** @var NodeFactory $nodeFactory */ $nodeFactory = $this->globalOptions['nodeFactory']; $childResult = $nodeFactory->create($options)->render(); $theTitle = htmlspecialchars($fakeParameterArray['fieldConf']['label']); $defInfo = array(); if (!$flexFormNoEditDefaultLanguage) { $previewLanguages = $this->globalOptions['additionalPreviewLanguages']; foreach ($previewLanguages as $previewLanguage) { $defInfo[] = '<div class="t3-form-original-language">'; $defInfo[] = FormEngineUtility::getLanguageIcon($table, $row, 'v' . $previewLanguage['ISOcode']); $defInfo[] = $this->previewFieldValue($flexFormRowData[$flexFormFieldName]['v' . $previewLanguage['ISOcode']], $fakeParameterArray['fieldConf'], $fieldName); $defInfo[] = '</div>'; } } $languageIcon = ''; if ($vDEFkey !== 'vDEF') { $languageIcon = FormEngineUtility::getLanguageIcon($table, $row, $vDEFkey); } // Possible line breaks in the label through xml: \n => <br/>, usage of nl2br() not possible, so it's done through str_replace (?!) $processedTitle = str_replace('\\n', '<br />', $theTitle); // @todo: Similar to the processing within SingleElementContainer ... use it from there?! $html = array(); $html[] = '<div class="form-section">'; $html[] = '<div class="form-group t3js-formengine-palette-field t3js-formengine-validation-marker">'; $html[] = '<label class="t3js-formengine-label">'; $html[] = $languageIcon; $html[] = BackendUtility::wrapInHelp($parameterArray['_cshKey'], $flexFormFieldName, $processedTitle); $html[] = '</label>'; $html[] = '<div class="t3js-formengine-field-item">'; $html[] = $childResult['html']; $html[] = implode(LF, $defInfo); $html[] = $this->renderVDEFDiff($flexFormRowData[$flexFormFieldName], $vDEFkey); $html[] = '</div>'; $html[] = '</div>'; $html[] = '</div>'; $resultArray['html'] .= implode(LF, $html); $childResult['html'] = ''; $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult); } } return $resultArray; }
/** * Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent. * * @param string $type Defines the type 'localize' or 'synchronize' (string) or a single uid to be localized (int) * @param int $inlineFirstPid Inline first pid * @return array An array to be used for JSON */ protected function renderInlineSynchronizeLocalizeRecords($type, $inlineFirstPid) { $jsonArray = FALSE; if (GeneralUtility::inList('localize,synchronize', $type) || MathUtility::canBeInterpretedAsInteger($type)) { $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class); // The parent level: $parent = $this->inlineStackProcessor->getStructureLevel(-1); $current = $this->inlineStackProcessor->getUnstableStructure(); $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']); $cmd = array(); $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type; /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */ $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class); $tce->stripslashes_values = FALSE; $tce->start(array(), $cmd); $tce->process_cmdmap(); $oldItemList = $parentRecord[$parent['field']]; $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parent['field']]; $jsonArray = array('scriptCall' => array()); $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid); $nameObjectForeignTable = $nameObject . '-' . $current['table']; // Get the name of the field pointing to the original record: $transOrigPointerField = $GLOBALS['TCA'][$current['table']]['ctrl']['transOrigPointerField']; // Get the name of the field used as foreign selector (if any): $foreignSelector = isset($parent['config']['foreign_selector']) && $parent['config']['foreign_selector'] ? $parent['config']['foreign_selector'] : FALSE; // Convert lists to array with uids of child records: $oldItems = FormEngineUtility::getInlineRelatedRecordsUidArray($oldItemList); $newItems = FormEngineUtility::getInlineRelatedRecordsUidArray($newItemList); // Determine the items that were localized or localized: $removedItems = array_diff($oldItems, $newItems); $localizedItems = array_diff($newItems, $oldItems); // Set the items that should be removed in the forms view: foreach ($removedItems as $item) { $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $item) . ', {forceDirectRemoval: true});'; } // Set the items that should be added in the forms view: $html = ''; $resultArray = NULL; // @todo: This should be another container ... foreach ($localizedItems as $item) { $row = $inlineRelatedRecordResolver->getRecord($current['table'], $item); $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($row[$foreignSelector]) : 'null'; $options = $this->getConfigurationOptionsForChildElements(); $options['databaseRow'] = array('uid' => $parent['uid']); $options['inlineFirstPid'] = $inlineFirstPid; $options['inlineRelatedRecordToRender'] = $row; $options['inlineRelatedRecordConfig'] = $parent['config']; $options['inlineStructure'] = $this->inlineStackProcessor->getStructure(); $options['isAjaxContext'] = TRUE; $options['renderType'] = 'inlineRecordContainer'; $childArray = $this->nodeFactory->create($options)->render(); $html .= $childArray['html']; $childArray['html'] = ''; // @todo: Obsolete if a container and copied from AbstractContainer for now if ($resultArray === NULL) { $resultArray = $childArray; } else { if (!empty($childArray['extJSCODE'])) { $resultArray['extJSCODE'] .= LF . $childArray['extJSCODE']; } foreach ($childArray['additionalJavaScriptPost'] as $value) { $resultArray['additionalJavaScriptPost'][] = $value; } foreach ($childArray['additionalJavaScriptSubmit'] as $value) { $resultArray['additionalJavaScriptSubmit'][] = $value; } if (!empty($childArray['inlineData'])) { $resultArrayInlineData = $resultArray['inlineData']; $childInlineData = $childArray['inlineData']; ArrayUtility::mergeRecursiveWithOverrule($resultArrayInlineData, $childInlineData); $resultArray['inlineData'] = $resultArrayInlineData; } } $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($item) . ', null, ' . $selectedValue . ');'; // Remove possible virtual records in the form which showed that a child records could be localized: if (isset($row[$transOrigPointerField]) && $row[$transOrigPointerField]) { $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $row[$transOrigPointerField] . '_div') . ');'; } } if (!empty($html)) { $jsonArray['data'] = $html; array_unshift($jsonArray['scriptCall'], 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records') . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', json.data);'); } $this->mergeResult($resultArray); $jsonArray = $this->getInlineAjaxCommonScriptCalls($jsonArray, $parent['config'], $inlineFirstPid); } return $jsonArray; }
/** * Determine label of a single field (not a palette label) * * @param string $fieldName The field name to calculate the label for * @param string $labelFromShowItem Given label, typically from show item configuration * @return string Field label */ protected function getSingleFieldLabel($fieldName, $labelFromShowItem) { $languageService = $this->getLanguageService(); $table = $this->globalOptions['table']; $label = $labelFromShowItem; if (!empty($GLOBALS['TCA'][$table]['columns'][$fieldName]['label'])) { $label = $GLOBALS['TCA'][$table]['columns'][$fieldName]['label']; } if (!empty($labelFromShowItem)) { $label = $labelFromShowItem; } $fieldTSConfig = FormEngineUtility::getTSconfigForTableRow($table, $this->globalOptions['databaseRow'], $fieldName); if (!empty($fieldTSConfig['label'])) { $label = $fieldTSConfig['label']; } if (!empty($fieldTSConfig['label.'][$languageService->lang])) { $label = $fieldTSConfig['label.'][$languageService->lang]; } return $languageService->sL($label); }
/** * Creates a selectorbox list (renderMode = "singlebox") * * @param string $table See getSingleField_typeSelect() * @param string $field See getSingleField_typeSelect() * @param array $row See getSingleField_typeSelect() * @param array $parameterArray See getSingleField_typeSelect() * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience) * @param array $selItems Items available for selection * @param string $noMatchingLabel Label for no-matching-value * @return string The HTML code for the item */ protected function getSingleField_typeSelect_singlebox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) { $languageService = $this->getLanguageService(); // Get values in an array (and make unique, which is fine because there can be no duplicates anyway): $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($parameterArray['itemFormElValue'])); $item = ''; $disabled = ''; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = ' disabled="disabled"'; } // Traverse the Array of selector box items: $opt = array(); // Used to accumulate the JS needed to restore the original selection. $restoreCmd = array(); $c = 0; foreach ($selItems as $p) { // Selected or not by default: $sM = ''; if (isset($itemArray[$p[1]])) { $sM = ' selected="selected"'; $restoreCmd[] = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'] . '[]') . '].options[' . $c . '].selected=1;'; unset($itemArray[$p[1]]); } // Non-selectable element: $nonSel = ''; if ((string) $p[1] === '--div--') { $nonSel = ' onclick="this.selected=0;" class="formcontrol-select-divider"'; } // Icon style for option tag: $styleAttrValue = ''; if ($config['iconsInOptionTags']) { $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]); } // Compile <option> tag: $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM . $nonSel . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>' . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>'; $c++; } // Remaining values: if (!empty($itemArray) && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) { foreach ($itemArray as $theNoMatchValue => $temp) { // Compile <option> tag: array_unshift($opt, '<option value="' . htmlspecialchars($theNoMatchValue) . '" selected="selected">' . htmlspecialchars(@sprintf($noMatchingLabel, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</option>'); } } // Compile selector box: $sOnChange = implode('', $parameterArray['fieldChangeFunc']); $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ''; $size = (int) $config['size']; $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect'; $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size; $selectBox = '<select id="' . str_replace('.', '', uniqid($cssPrefix, TRUE)) . '" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '[]" ' . 'class="form-control ' . $cssPrefix . '"' . ($size ? ' size="' . $size . '" ' : '') . ' multiple="multiple" onchange="' . htmlspecialchars($sOnChange) . '"' . $parameterArray['onFocus'] . ' ' . $this->getValidationDataAsDataAttribute($config) . $selector_itemListStyle . $disabled . '> ' . implode(' ', $opt) . ' </select>'; // Add an empty hidden field which will send a blank value if all items are unselected. if (!$disabled) { $item .= '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="" />'; } // Put it all into a table: $onClick = htmlspecialchars('document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'] . '[]') . '].selectedIndex=-1;' . implode('', $restoreCmd) . ' return false;'); $width = $this->formMaxWidth($this->defaultInputWidth); $item .= ' <div class="form-control-wrap" ' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '> <div class="form-wizards-wrap form-wizards-aside"> <div class="form-wizards-element"> ' . $selectBox . ' </div> <div class="form-wizards-items"> <a href="#" class="btn btn-default" onclick="' . $onClick . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')) . '">' . IconUtility::getSpriteIcon('actions-edit-undo') . '</a> </div> </div> </div> <p> <em>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.holdDownCTRL')) . '</em> </p> '; return $item; }
/** * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc. * Later on the command-icons are inserted here. * * @param string $parentUid The uid of the parent (embedding) record (uid or NEW...) * @param string $foreign_table The foreign_table we create a header for * @param array $data Current data * @param array $config content of $PA['fieldConf']['config'] * @param bool $isVirtualRecord * @return string The HTML code of the header */ protected function renderForeignRecordHeader($parentUid, $foreign_table, $data, $config, $isVirtualRecord = false) { $rec = $data['databaseRow']; // Init: $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $objectId = $domObjectId . '-' . $foreign_table . '-' . $rec['uid']; // We need the returnUrl of the main script when loading the fields via AJAX-call (to correct wizard code, so include it as 3rd parameter) // Pre-Processing: $isOnSymmetricSide = RelationHandler::isOnSymmetricSide($parentUid, $config, $rec); $hasForeignLabel = (bool) (!$isOnSymmetricSide && $config['foreign_label']); $hasSymmetricLabel = (bool) $isOnSymmetricSide && $config['symmetric_label']; // Get the record title/label for a record: // Try using a self-defined user function only for formatted labels if (isset($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc'])) { $params = array('table' => $foreign_table, 'row' => $rec, 'title' => '', 'isOnSymmetricSide' => $isOnSymmetricSide, 'options' => isset($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc_options']) ? $GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc_options'] : array(), 'parent' => array('uid' => $parentUid, 'config' => $config)); // callUserFunction requires a third parameter, but we don't want to give $this as reference! $null = null; GeneralUtility::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc'], $params, $null); $recTitle = $params['title']; // Try using a normal self-defined user function } elseif (isset($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'])) { $recTitle = $data['recordTitle']; } elseif ($hasForeignLabel || $hasSymmetricLabel) { $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label']; // Render title for everything else than group/db: if (isset($this->data['processedTca']['columns'][$titleCol]['config']['type']) && $this->data['processedTca']['columns'][$titleCol]['config']['type'] === 'group' && isset($this->data['processedTca']['columns'][$titleCol]['config']['internal_type']) && $this->data['processedTca']['columns'][$titleCol]['config']['internal_type'] === 'db') { $recTitle = BackendUtility::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false); } else { // $recTitle could be something like: "tx_table_123|...", $valueParts = GeneralUtility::trimExplode('|', $rec[$titleCol]); $itemParts = GeneralUtility::revExplode('_', $valueParts[0], 2); $recTemp = BackendUtility::getRecordWSOL($itemParts[0], $itemParts[1]); $recTitle = BackendUtility::getRecordTitle($itemParts[0], $recTemp, false); } $recTitle = BackendUtility::getRecordTitlePrep($recTitle); if (trim($recTitle) === '') { $recTitle = BackendUtility::getNoRecordTitle(true); } } else { $recTitle = BackendUtility::getRecordTitle($foreign_table, FormEngineUtility::databaseRowCompatibility($rec), true); } $altText = BackendUtility::getRecordIconAltText($rec, $foreign_table); $iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon' . '">' . $this->iconFactory->getIconForRecord($foreign_table, $rec, Icon::SIZE_SMALL)->render() . '</span>'; $label = '<span id="' . $objectId . '_label">' . $recTitle . '</span>'; $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $data, $config, $isVirtualRecord); $thumbnail = false; // Renders a thumbnail for the header if (!empty($config['appearance']['headerThumbnail']['field'])) { $fieldValue = $rec[$config['appearance']['headerThumbnail']['field']]; $firstElement = array_shift(GeneralUtility::trimExplode('|', array_shift(GeneralUtility::trimExplode(',', $fieldValue)))); $fileUid = array_pop(BackendUtility::splitTable_Uid($firstElement)); if (!empty($fileUid)) { $fileObject = ResourceFactory::getInstance()->getFileObject($fileUid); if ($fileObject && $fileObject->isMissing()) { $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject); $thumbnail = $flashMessage->render(); } elseif ($fileObject) { $imageSetup = $config['appearance']['headerThumbnail']; unset($imageSetup['field']); if (!empty($rec['crop'])) { $imageSetup['crop'] = $rec['crop']; } $imageSetup = array_merge(array('width' => '45', 'height' => '45c'), $imageSetup); $processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup); // Only use a thumbnail if the processing process was successful by checking if image width is set if ($processedImage->getProperty('width')) { $imageUrl = $processedImage->getPublicUrl(true); $thumbnail = '<img src="' . $imageUrl . '" ' . 'width="' . $processedImage->getProperty('width') . '" ' . 'height="' . $processedImage->getProperty('height') . '" ' . 'alt="' . htmlspecialchars($altText) . '" ' . 'title="' . htmlspecialchars($altText) . '">'; } } } } if (!empty($config['appearance']['headerThumbnail']['field']) && $thumbnail) { $mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>'; } else { $mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>'; } $header = $mediaContainer . ' <div class="form-irre-header-cell form-irre-header-body">' . $label . '</div> <div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>'; return $header; }
/** * Modify a single FlexForm sheet according to given configuration * * @param array $sheet Flexform sheet to manipulate * @param string $table The table name * @param string $tableField The field name * @param array $tableRow The record data * @param array $sheetConf Sheet configuration * @param array $nonExcludeFields Non-exclude-fields for this sheet * @return array Modified sheet * @see \TYPO3\CMS\Backend\Form\FlexFormsHelper::modifyFlexFormDS() */ public function modifySingleFlexFormSheet(array $sheet, $table, $tableField, array $tableRow, array $sheetConf, array $nonExcludeFields) { if (empty($sheet) || empty($table) || empty($tableField) || empty($tableRow)) { return $sheet; } // Modify fields foreach ($sheet as $fieldName => $field) { // Remove excluded fields if (!$GLOBALS['BE_USER']->isAdmin() && !empty($field['TCEforms']['exclude']) && empty($nonExcludeFields[$fieldName])) { unset($sheet[$fieldName]); continue; } // Stop here if no TSConfig was found for this field if (empty($sheetConf[$fieldName]) || !is_array($sheetConf[$fieldName])) { continue; } // Remove disabled fields if (!empty($sheetConf[$fieldName]['disabled'])) { unset($sheet[$fieldName]); continue; } $fieldConf = $sheetConf[$fieldName]; $removeItems = !empty($fieldConf['removeItems']) ? GeneralUtility::trimExplode(',', $fieldConf['removeItems'], TRUE) : array(); $keepItems = !empty($fieldConf['keepItems']) ? GeneralUtility::trimExplode(',', $fieldConf['keepItems'], TRUE) : array(); $renameItems = !empty($fieldConf['altLabels']) && is_array($fieldConf['altLabels']) ? $fieldConf['altLabels'] : array(); $changeIcons = !empty($fieldConf['altIcons']) && is_array($fieldConf['altIcons']) ? $fieldConf['altIcons'] : array(); $addItems = !empty($fieldConf['addItems']) && is_array($fieldConf['addItems']) ? $fieldConf['addItems'] : array(); unset($fieldConf['removeItems']); unset($fieldConf['keepItems']); unset($fieldConf['altLabels']); unset($fieldConf['altIcons']); unset($fieldConf['addItems']); // Manipulate field if (!empty($field['TCEforms']) && is_array($field['TCEforms'])) { $sheet[$fieldName]['TCEforms'] = $field['TCEforms']; ArrayUtility::mergeRecursiveWithOverrule($sheet[$fieldName]['TCEforms'], $fieldConf); } // Manipulate only select fields, other field types will stop here if (empty($field['TCEforms']['config']['type']) || $field['TCEforms']['config']['type'] != 'select' || $field['TCEforms']['config']['renderMode'] === 'tree') { continue; } // Getting the selector box items from system $selItems = FormEngineUtility::addSelectOptionsToItemArray(FormEngineUtility::initItemArray($field['TCEforms']), $field['TCEforms'], FormEngineUtility::getTSconfigForTableRow($table, $tableRow), $tableField); // Possibly filter some items $selItems = ArrayUtility::keepItemsInArray($selItems, $keepItems, function ($value) { return $value[1]; }); // Possibly add some items $selItems = FormEngineUtility::addItems($selItems, $addItems); // Process items by a user function if (!empty($field['TCEforms']['config']['itemsProcFunc'])) { $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class); $selItems = $dataPreprocessor->procItems($selItems, $fieldConf['config'], $field['TCEforms']['config'], $table, $tableRow, $tableField); } // Remove special configuration options after creating items to prevent double parsing foreach ($this->removeSelectConfig as $option) { unset($sheet[$fieldName]['TCEforms']['config'][$option]); } // Rename and remove items or change item icon in select if ((!empty($removeItems) || !empty($renameItems) || !empty($changeIcons)) && !empty($selItems) && is_array($selItems)) { foreach ($selItems as $itemKey => $itemConf) { // Option has no key, no manipulation possible if (!isset($itemConf[1])) { continue; } // Remove foreach ($removeItems as $removeKey => $removeValue) { if (strcasecmp($removeValue, $itemConf[1]) == 0) { unset($selItems[$itemKey]); unset($removeItems[$removeKey]); } } // Rename foreach ($renameItems as $renameKey => $renameValue) { if (strcasecmp($renameKey, $itemConf[1]) == 0) { $selItems[$itemKey][0] = htmlspecialchars($renameValue); unset($renameItems[$renameKey]); } } // Change icon foreach ($changeIcons as $iconKey => $iconValue) { if (strcasecmp($iconKey, $itemConf[1]) == 0) { $selItems[$itemKey][2] = $iconValue; unset($changeIcons[$iconKey]); } } } } $sheet[$fieldName]['TCEforms']['config']['items'] = $selItems; } return $sheet; }
/** * Gets the related records of the embedding item, this could be 1:n, m:n. * * @param string $table The table name of the record * @param string $itemList The list of related child records * @return array The records related to the parent item */ protected function getRelatedRecordsArray($table, $itemList) { $records = array(); $itemArray = FormEngineUtility::getInlineRelatedRecordsUidArray($itemList); // Perform modification of the selected items array: foreach ($itemArray as $uid) { // Get the records for this uid using \TYPO3\CMS\Backend\Form\DataPreprocessor if ($record = $this->getRecord($table, $uid)) { $records[$uid] = $record; } } return $records; }
/** * Main function, rendering the document with the iFrame with the RTE in. * * @return void */ public function main() { // Translate id to the workspace version: if ($versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($this->getBackendUserAuthentication()->workspace, $this->P['table'], $this->P['uid'], 'uid')) { $this->P['uid'] = $versionedRecord['uid']; } // If all parameters are available: if ($this->P['table'] && $this->P['field'] && $this->P['uid'] && $this->checkEditAccess($this->P['table'], $this->P['uid'])) { // Getting the raw record (we need only the pid-value from here...) $rawRecord = BackendUtility::getRecord($this->P['table'], $this->P['uid']); BackendUtility::fixVersioningPid($this->P['table'], $rawRecord); // override the default jumpToUrl $this->doc->JScodeArray['jumpToUrl'] = ' function jumpToUrl(URL,formEl) { if (document.editform) { if (!TBE_EDITOR.isFormChanged()) { window.location.href = URL; } else if (formEl) { if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1; } } else { window.location.href = URL; } } '; // Setting JavaScript of the pid value for viewing: if ($this->popView) { $this->doc->JScode = $this->doc->wrapScriptTags(BackendUtility::viewOnClick($rawRecord['pid'], '', BackendUtility::BEgetRootLine($rawRecord['pid']))); } // Initialize FormEngine - for rendering the field: /** @var FormEngine $formEngine */ $formEngine = GeneralUtility::makeInstance(FormEngine::class); // SPECIAL: Disables all wizards - we are NOT going to need them. $formEngine->disableWizards = 1; // Fetching content of record: /** @var DataPreprocessor $dataPreprocessor */ $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class); $dataPreprocessor->lockRecords = 1; $dataPreprocessor->fetchRecord($this->P['table'], $this->P['uid'], ''); // Getting the processed record content out: $processedRecord = reset($dataPreprocessor->regTableItems_data); $processedRecord['uid'] = $this->P['uid']; $processedRecord['pid'] = $rawRecord['pid']; // TSconfig, setting width: $fieldTSConfig = FormEngineUtility::getTSconfigForTableRow($this->P['table'], $processedRecord, $this->P['field']); if ((string) $fieldTSConfig['RTEfullScreenWidth'] !== '') { $width = $fieldTSConfig['RTEfullScreenWidth']; } else { $width = '100%'; } // Get the form field and wrap it in the table with the buttons: $formContent = $formEngine->getSoloField($this->P['table'], $processedRecord, $this->P['field']); $formContent = ' <!-- RTE wizard: --> <table border="0" cellpadding="0" cellspacing="0" width="' . $width . '" id="typo3-rtewizard"> <tr> <td width="' . $width . '" colspan="2" id="c-formContent">' . $formContent . '</td> <td></td> </tr> </table>'; // Adding hidden fields: $formContent .= '<input type="hidden" name="redirect" value="' . htmlspecialchars($this->R_URI) . '" /> <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />' . FormEngine::getHiddenTokenField('tceAction'); // Finally, add the whole setup: $this->content .= $formEngine->printNeededJSFunctions_top() . $formContent . $formEngine->printNeededJSFunctions(); } else { // ERROR: $this->content .= $this->doc->section($this->getLanguageService()->getLL('forms_title'), '<span class="text-danger">' . $this->getLanguageService()->getLL('table_noData', TRUE) . '</span>', 0, 1); } // Setting up the buttons and markers for docHeader $docHeaderButtons = $this->getButtons(); $markers['CONTENT'] = $this->content; // Build the <body> for the module $this->content = $this->doc->startPage(''); $this->content .= $this->doc->moduleBody(array(), $docHeaderButtons, $markers); $this->content .= $this->doc->endPage(); $this->content = $this->doc->insertStylesAndJS($this->content); }
/** * Renders the display of default language record content around current field. * Will render content if any is found in the internal array, $this->defaultLanguageData, * depending on registerDefaultLanguageData() being called prior to this. * * @param string $table Table name of the record being edited * @param string $field Field name represented by $item * @param array $row Record array of the record being edited * @param string $item HTML of the form field. This is what we add the content to. * @return string Item string returned again, possibly with the original value added to. */ protected function renderDefaultLanguageContent($table, $field, $row, $item) { if (is_array($this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']])) { $defaultLanguageValue = BackendUtility::getProcessedValue($table, $field, $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']][$field], 0, 1, FALSE, $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']]['uid']); $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]; // Don't show content if it's for IRRE child records: if ($fieldConfig['config']['type'] !== 'inline') { if ($defaultLanguageValue !== '') { $item .= '<div class="t3-form-original-language">' . FormEngineUtility::getLanguageIcon($table, $row, 0) . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode']) . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>'; } $additionalPreviewLanguages = $this->globalOptions['additionalPreviewLanguages']; foreach ($additionalPreviewLanguages as $previewLanguage) { $defaultLanguageValue = BackendUtility::getProcessedValue($table, $field, $this->globalOptions['additionalPreviewLanguageData'][$table . ':' . $row['uid']][$previewLanguage['uid']][$field], 0, 1); if ($defaultLanguageValue !== '') { $item .= '<div class="t3-form-original-language">' . FormEngineUtility::getLanguageIcon($table, $row, 'v' . $previewLanguage['ISOcode']) . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode']) . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>'; } } } } return $item; }
/** * If the select field is build by a foreign_table the related UIDs * will be returned. * * Otherwise the label of the currently selected value will be written * to the alternativeFieldValue class property. * * @param array $fieldConfig The "config" section of the TCA for the current select field. * @param string $fieldName The name of the select field. * @param string $value The current value in the local record, usually a comma separated list of selected values. * @return array Array of related UIDs. */ protected function getRelatedSelectFieldUids(array $fieldConfig, $fieldName, $value) { $relatedUids = array(); $isTraversable = FALSE; if (isset($fieldConfig['foreign_table'])) { $isTraversable = TRUE; // if a foreign_table is used we pre-filter the records for performance $fieldConfig['foreign_table_where'] .= ' AND ' . $fieldConfig['foreign_table'] . '.uid IN (' . $value . ')'; } $PA = array(); $PA['fieldConf']['config'] = $fieldConfig; $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($this->currentTable, $this->currentRow, $fieldName); $PA['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($PA['fieldConf']['config'], $PA['fieldTSConfig']); $selectItemArray = FormEngineUtility::getSelectItems($this->currentTable, $fieldName, $this->currentRow, $PA); if ($isTraversable && !empty($selectItemArray)) { $this->currentTable = $fieldConfig['foreign_table']; $relatedUids = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value); } else { $selectedLabels = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value, 1, TRUE); if (count($selectedLabels) === 1) { $this->alternativeFieldValue = $selectedLabels[0]; $this->forceAlternativeFieldValueUse = TRUE; } } return $relatedUids; }
/** * Adds localizations or synchronizes the locations of all child records. * Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent. * * @param ServerRequestInterface $request the incoming request * @param ResponseInterface $response the empty response * @return ResponseInterface the filled response */ public function synchronizeLocalizeAction(ServerRequestInterface $request, ResponseInterface $response) { $ajaxArguments = isset($request->getParsedBody()['ajax']) ? $request->getParsedBody()['ajax'] : $request->getQueryParams()['ajax']; $domObjectId = $ajaxArguments[0]; $type = $ajaxArguments[1]; /** @var InlineStackProcessor $inlineStackProcessor */ $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class); // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config: $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId); $inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']); $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId); $jsonArray = false; if ($type === 'localize' || $type === 'synchronize' || MathUtility::canBeInterpretedAsInteger($type)) { // Parent, this table embeds the child table $parent = $inlineStackProcessor->getStructureLevel(-1); $parentFieldName = $parent['field']; // Child, a record from this table should be rendered $child = $inlineStackProcessor->getUnstableStructure(); $formDataCompilerInputForParent = ['vanillaUid' => (int) $parent['uid'], 'command' => 'edit', 'tableName' => $parent['table'], 'databaseRow' => ['uid' => (int) $parent['uid']], 'inlineFirstPid' => $inlineFirstPid, 'columnsToProcess' => [$parentFieldName], 'inlineStructure' => $inlineStackProcessor->getStructure(), 'inlineCompileExistingChildren' => false]; /** @var TcaDatabaseRecord $formDataGroup */ $formDataGroup = GeneralUtility::makeInstance(InlineParentRecord::class); /** @var FormDataCompiler $formDataCompiler */ $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup); $parentData = $formDataCompiler->compile($formDataCompilerInputForParent); $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config']; $oldItemList = $parentData['databaseRow'][$parentFieldName]; $cmd = array(); $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type; /** @var $tce DataHandler */ $tce = GeneralUtility::makeInstance(DataHandler::class); $tce->stripslashes_values = false; $tce->start(array(), $cmd); $tce->process_cmdmap(); $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parentFieldName]; $jsonArray = array('data' => '', 'stylesheetFiles' => [], 'scriptCall' => []); $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid); $nameObjectForeignTable = $nameObject . '-' . $child['table']; $oldItems = FormEngineUtility::getInlineRelatedRecordsUidArray($oldItemList); $newItems = FormEngineUtility::getInlineRelatedRecordsUidArray($newItemList); // Set the items that should be removed in the forms view: $removedItems = array_diff($oldItems, $newItems); foreach ($removedItems as $childUid) { $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $childUid) . ', {forceDirectRemoval: true});'; } $localizedItems = array_diff($newItems, $oldItems); foreach ($localizedItems as $childUid) { $childData = $this->compileChild($parentData, $parentFieldName, (int) $childUid); $childData['inlineParentUid'] = (int) $parent['uid']; // @todo: needed? $childData['inlineStructure'] = $inlineStackProcessor->getStructure(); // @todo: needed? $childData['inlineExpandCollapseStateArray'] = $parentData['inlineExpandCollapseStateArray']; $childData['renderType'] = 'inlineRecordContainer'; $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class); $childResult = $nodeFactory->create($childData)->render(); $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult); // Get the name of the field used as foreign selector (if any): $foreignSelector = isset($parentConfig['foreign_selector']) && $parentConfig['foreign_selector'] ? $parentConfig['foreign_selector'] : false; $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($childData['databaseRow'][$foreignSelector]) : 'null'; if (is_array($selectedValue)) { $selectedValue = $selectedValue[0]; } $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($childUid) . ', null, ' . $selectedValue . ');'; // Remove possible virtual records in the form which showed that a child records could be localized: $transOrigPointerFieldName = $GLOBALS['TCA'][$childData['table']]['ctrl']['transOrigPointerField']; if (isset($childData['databaseRow'][$transOrigPointerFieldName]) && $childData['databaseRow'][$transOrigPointerFieldName]) { $transOrigPointerField = $childData['databaseRow'][$transOrigPointerFieldName]; if (is_array($transOrigPointerField)) { $transOrigPointerField = $transOrigPointerField[0]; } $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $transOrigPointerField . '_div') . ');'; } if (!empty($childResult['html'])) { array_unshift($jsonArray['scriptCall'], 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records') . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', json.data);'); } } } $response->getBody()->write(json_encode($jsonArray)); return $response; }
/** * This will render a checkbox or an array of checkboxes * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $config = $this->globalOptions['parameterArray']['fieldConf']['config']; $html = ''; $disabled = FALSE; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = TRUE; } // Traversing the array of items $items = FormEngineUtility::initItemArray($this->globalOptions['parameterArray']['fieldConf']); if ($config['itemsProcFunc']) { $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class); $items = $dataPreprocessor->procItems($items, $this->globalOptions['parameterArray']['fieldTSConfig']['itemsProcFunc.'], $config, $this->globalOptions['table'], $this->globalOptions['databaseRow'], $this->globalOptions['fieldName']); } $numberOfItems = count($items); if ($numberOfItems === 0) { $items[] = array('', ''); $numberOfItems = 1; } $formElementValue = (int) $this->globalOptions['parameterArray']['itemFormElValue']; $cols = (int) $config['cols']; if ($cols > 1) { $colWidth = (int) floor(12 / $cols); $colClass = "col-md-12"; $colClear = array(); if ($colWidth == 6) { $colClass = "col-sm-6"; $colClear = array(2 => 'visible-sm-block visible-md-block visible-lg-block'); } elseif ($colWidth === 4) { $colClass = "col-sm-4"; $colClear = array(3 => 'visible-sm-block visible-md-block visible-lg-block'); } elseif ($colWidth === 3) { $colClass = "col-sm-6 col-md-3"; $colClear = array(2 => 'visible-sm-block', 4 => 'visible-md-block visible-lg-block'); } elseif ($colWidth <= 2) { $colClass = "checkbox-column col-sm-6 col-md-3 col-lg-2"; $colClear = array(2 => 'visible-sm-block', 4 => 'visible-md-block', 6 => 'visible-lg-block'); } $html .= '<div class="checkbox-row row">'; for ($counter = 0; $counter < $numberOfItems; $counter++) { // use "default" for typical single checkboxes $tsConfigKey = $numberOfItems === 1 ? 'default' : $items[$counter][1]; // useful for e.g. pages.l18n_cfg, where there is no value set if ($tsConfigKey === '') { $tsConfigKey = $counter; } if (isset($this->globalOptions['parameterArray']['fieldTSConfig']['altLabels.'][$tsConfigKey])) { $label = $this->getLanguageService()->sL($this->globalOptions['parameterArray']['fieldTSConfig']['altLabels.'][$tsConfigKey]); } else { $label = $items[$counter][0]; } $html .= '<div class="checkbox-column ' . $colClass . '">' . $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->globalOptions['parameterArray'], $disabled) . '</div>'; if ($counter + 1 < $numberOfItems && !empty($colClear)) { foreach ($colClear as $rowBreakAfter => $clearClass) { if (($counter + 1) % $rowBreakAfter === 0) { $html .= '<div class="clearfix ' . $clearClass . '"></div>'; } } } } $html .= '</div>'; } else { for ($counter = 0; $counter < $numberOfItems; $counter++) { // use "default" for typical single checkboxes $tsConfigKey = $numberOfItems === 1 ? 'default' : $items[$counter][1]; // useful for e.g. pages.l18n_cfg, where there is no value set if ($tsConfigKey === '') { $tsConfigKey = $counter; } if (isset($this->globalOptions['parameterArray']['fieldTSConfig']['altLabels.'][$tsConfigKey])) { $label = $this->getLanguageService()->sL($this->globalOptions['parameterArray']['fieldTSConfig']['altLabels.'][$tsConfigKey]); } else { $label = $items[$counter][0]; } $html .= $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->globalOptions['parameterArray'], $disabled); } } if (!$disabled) { $html .= '<input type="hidden" name="' . $this->globalOptions['parameterArray']['itemFormElName'] . '" value="' . htmlspecialchars($formElementValue) . '" />'; } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; return $resultArray; }
/** * Render check boxes * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $html = []; // Field configuration from TCA: $parameterArray = $this->data['parameterArray']; $config = $parameterArray['fieldConf']['config']; $disabled = !empty($config['readOnly']); $selItems = $config['items']; if (!empty($selItems)) { // Get values in an array (and make unique, which is fine because there can be no duplicates anyway): $itemArray = array_flip($parameterArray['itemFormElValue']); // Traverse the Array of selector box items: $groups = array(); $currentGroup = 0; $c = 0; $sOnChange = ''; if (!$disabled) { $sOnChange = implode('', $parameterArray['fieldChangeFunc']); // Used to accumulate the JS needed to restore the original selection. foreach ($selItems as $p) { // Non-selectable element: if ($p[1] === '--div--') { $selIcon = ''; if (isset($p[2]) && $p[2] != 'empty-empty') { $selIcon = FormEngineUtility::getIconHtml($p[2]); } $currentGroup++; $groups[$currentGroup]['header'] = array('icon' => $selIcon, 'title' => htmlspecialchars($p[0])); } else { // Check if some help text is available // Since TYPO3 4.5 help text is expected to be an associative array // with two key, "title" and "description" // For the sake of backwards compatibility, we test if the help text // is a string and use it as a description (this could happen if items // are modified with an itemProcFunc) $hasHelp = false; $help = ''; $helpArray = array(); if (!empty($p[3])) { $hasHelp = true; if (is_array($p[3])) { $helpArray = $p[3]; } else { $helpArray['description'] = $p[3]; } } if ($hasHelp) { $help = BackendUtility::wrapInHelp('', '', '', $helpArray); } // Selected or not by default: $checked = 0; if (isset($itemArray[$p[1]])) { $checked = 1; unset($itemArray[$p[1]]); } // Build item array $groups[$currentGroup]['items'][] = array('id' => StringUtility::getUniqueId('select_checkbox_row_'), 'name' => $parameterArray['itemFormElName'] . '[' . $c . ']', 'value' => $p[1], 'checked' => $checked, 'disabled' => false, 'class' => '', 'icon' => !empty($p[2]) ? FormEngineUtility::getIconHtml($p[2]) : $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render(), 'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', false), 'help' => $help); $c++; } } } // Add an empty hidden field which will send a blank value if all items are unselected. $html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">'; // Building the checkboxes foreach ($groups as $groupKey => $group) { $groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey; $html[] = '<div class="panel panel-default">'; if (is_array($group['header'])) { $html[] = '<div class="panel-heading">'; $html[] = '<a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="false" aria-controls="' . $groupId . '">'; $html[] = $group['header']['icon']; $html[] = $group['header']['title']; $html[] = '</a>'; $html[] = '</div>'; } if (is_array($group['items']) && !empty($group['items'])) { $tableRows = []; $checkGroup = array(); $uncheckGroup = array(); $resetGroup = array(); // Render rows foreach ($group['items'] as $item) { $tableRows[] = '<tr class="' . $item['class'] . '">'; $tableRows[] = '<td class="col-checkbox">'; $tableRows[] = '<input type="checkbox" ' . 'id="' . $item['id'] . '" ' . 'name="' . htmlspecialchars($item['name']) . '" ' . 'value="' . htmlspecialchars($item['value']) . '" ' . 'onclick="' . htmlspecialchars($sOnChange) . '" ' . ($item['checked'] ? 'checked=checked ' : '') . ($item['disabled'] ? 'disabled=disabled ' : '') . $parameterArray['onFocus'] . '>'; $tableRows[] = '</td>'; $tableRows[] = '<td class="col-icon">'; $tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>'; $tableRows[] = '</td>'; $tableRows[] = '<td class="col-title">'; $tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label>'; $tableRows[] = '</td>'; $tableRows[] = '<td>' . $item['help'] . '</td>'; $tableRows[] = '</tr>'; $checkGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=1;'; $uncheckGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=0;'; $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=' . $item['checked'] . ';'; } // Build toggle group checkbox $toggleGroupCheckbox = ''; if (!empty($resetGroup)) { $toggleGroupCheckbox = '<input type="checkbox" ' . 'class="checkbox" ' . 'onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}">'; } // Build reset group button $resetGroupBtn = ''; if (!empty($resetGroup)) { $title = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection', true); $resetGroupBtn = '<a href="#" ' . 'class="btn btn-default" ' . 'onclick="' . implode('', $resetGroup) . ' return false;" ' . 'title="' . $title . '">' . $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render() . ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . '</a>'; } if (is_array($group['header'])) { $html[] = '<div id="' . $groupId . '" class="panel-collapse collapse" role="tabpanel">'; } $html[] = '<div class="table-fit">'; $html[] = '<table class="table table-transparent table-hover">'; $html[] = '<thead>'; $html[] = '<tr>'; $html[] = '<th class="col-checkbox">' . $toggleGroupCheckbox . '</th>'; $html[] = '<th class="col-icon"></th>'; $html[] = '<th class="text-right" colspan="2">' . $resetGroupBtn . '</th>'; $html[] = '</tr>'; $html[] = '</thead>'; $html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>'; $html[] = '</table>'; $html[] = '</div>'; if (is_array($group['header'])) { $html[] = '</div>'; } } $html[] = '</div>'; } } if (!$disabled) { $html = $this->renderWizards(array(implode(LF, $html)), $config['wizards'], $this->data['tableName'], $this->data['databaseRow'], $this->data['fieldName'], $parameterArray, $parameterArray['itemFormElName'], BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])); } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; return $resultArray; }
/** * Render single element * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $table = $this->data['tableName']; $field = $this->data['fieldName']; $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; $config = $parameterArray['fieldConf']['config']; $selectItems = $parameterArray['fieldConf']['config']['items']; // Check against inline uniqueness /** @var InlineStackProcessor $inlineStackProcessor */ $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class); $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']); $uniqueIds = null; if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) { $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix(); if ($this->data['inlineParentConfig']['foreign_table'] === $table && $this->data['inlineParentConfig']['foreign_unique'] === $field) { $uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used']; $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,' . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ',' . GeneralUtility::quoteJSvalue($inlineFormName) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ');'; } // hide uid of parent record for symmetric relations if ($this->data['inlineParentConfig']['foreign_table'] === $table && ($this->data['inlineParentConfig']['foreign_field'] === $field || $this->data['inlineParentConfig']['symmetric_field'] === $field)) { $uniqueIds[] = $this->data['inlineParentUid']; } } // Initialization: $selectId = StringUtility::getUniqueId('tceforms-select-'); $selectedIcon = ''; $size = (int) $config['size']; // Style set on <select/> $options = ''; $disabled = false; if (!empty($config['readOnly'])) { $disabled = true; } // Prepare groups $selectItemCounter = 0; $selectItemGroupCount = 0; $selectItemGroups = array(); $selectIcons = array(); $selectedValue = ''; $hasIcons = false; if (!empty($parameterArray['itemFormElValue'])) { $selectedValue = (string) $parameterArray['itemFormElValue'][0]; } foreach ($selectItems as $item) { if ($item[1] === '--div--') { // IS OPTGROUP if ($selectItemCounter !== 0) { $selectItemGroupCount++; } $selectItemGroups[$selectItemGroupCount]['header'] = array('title' => $item[0]); } else { // IS ITEM $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', false); $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : ''; $selected = $selectedValue === (string) $item[1]; if ($selected) { $selectedIcon = $icon; } $selectItemGroups[$selectItemGroupCount]['items'][] = array('title' => $title, 'value' => $item[1], 'icon' => $icon, 'selected' => $selected, 'index' => $selectItemCounter); // ICON if ($icon) { $selectIcons[] = array('title' => $title, 'icon' => $icon, 'index' => $selectItemCounter); } $selectItemCounter++; } } // Fallback icon // @todo: assign a special icon for non matching values? if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) { $selectedIcon = $selectItemGroups[0]['items'][0]['icon']; } // Process groups foreach ($selectItemGroups as $selectItemGroup) { // suppress groups without items if (empty($selectItemGroup['items'])) { continue; } $optionGroup = is_array($selectItemGroup['header']); $options .= $optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : ''; if (is_array($selectItemGroup['items'])) { foreach ($selectItemGroup['items'] as $item) { $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' . htmlspecialchars($item['icon']) . '"' . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>'; } $hasIcons = !empty($item['icon']); } $options .= $optionGroup ? '</optgroup>' : ''; } // Build the element $html = ['<div class="form-control-wrap">']; if ($hasIcons) { $html[] = '<div class="input-group">'; $html[] = '<span class="input-group-addon input-group-icon">'; $html[] = $selectedIcon; $html[] = '</span>'; } $html[] = '<select' . ' id="' . $selectId . '"' . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"' . $this->getValidationDataAsDataAttribute($config) . ' class="form-control form-control-adapt"' . ($size ? ' size="' . $size . '"' : '') . ($disabled ? ' disabled="disabled"' : '') . '>'; $html[] = $options; $html[] = '</select>'; if ($hasIcons) { $html[] = '</div>'; } $html[] = '</div>'; // Create icon table: if (!empty($selectIcons) && !empty($config['showIconTable'])) { $selectIconColumns = (int) $config['selicon_cols']; if (!$selectIconColumns) { $selectIconColumns = count($selectIcons); } $selectIconColumns = $selectIconColumns > 12 ? 12 : $selectIconColumns; $selectIconRows = ceil(count($selectIcons) / $selectIconColumns); $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, ''); $html[] = '<div class="t3js-forms-select-single-icons table-icons table-fit table-fit-inline-block">'; $html[] = '<table class="table table-condensed table-white table-center">'; $html[] = '<tbody>'; $html[] = '<tr>'; foreach ($selectIcons as $i => $selectIcon) { if ($i % $selectIconColumns === 0 && $i !== 0) { $html[] = '</tr>'; $html[] = '<tr>'; } $html[] = '<td>'; if (is_array($selectIcon)) { $html[] = '<a href="#" title="' . $selectIcon['title'] . '" data-select-index="' . $selectIcon['index'] . '">'; $html[] = $selectIcon['icon']; $html[] = '</a>'; } $html[] = '</td>'; } $html[] = '</tr>'; $html[] = '</tbody>'; $html[] = '</table>'; $html[] = '</div>'; } $html = implode(LF, $html); // Wizards: if (!$disabled) { $html = $this->renderWizards(array($html), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])); } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, ['function(SelectSingleElement) {', 'SelectSingleElement.initialize(', GeneralUtility::quoteJSvalue('#' . $selectId) . ',', '{', 'onChange: function() {', implode('', $parameterArray['fieldChangeFunc']), '}', '}', ');', '}'])]; return $resultArray; }
/** * Gets the list of available columns for a given page id * * @param int $id * @return array $tcaItems */ public function getColPosListItemsParsed($id) { $tsConfig = BackendUtility::getModTSconfig($id, 'TCEFORM.tt_content.colPos'); $tcaConfig = $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']; $tcaItems = $tcaConfig['items']; $tcaItems = FormEngineUtility::addItems($tcaItems, $tsConfig['properties']['addItems.']); if (isset($tcaConfig['itemsProcFunc']) && $tcaConfig['itemsProcFunc']) { $tcaItems = $this->addColPosListLayoutItems($id, $tcaItems); } foreach (GeneralUtility::trimExplode(',', $tsConfig['properties']['removeItems'], TRUE) as $removeId) { foreach ($tcaItems as $key => $item) { if ($item[1] == $removeId) { unset($tcaItems[$key]); } } } return $tcaItems; }
/** * Creates the editing form with FormEnigne, based on the input from GPvars. * * @return string HTML form elements wrapped in tables */ public function makeEditForm() { // Initialize variables: $this->elementsData = array(); $this->errorC = 0; $this->newC = 0; $editForm = ''; $trData = null; $beUser = $this->getBackendUser(); // Traverse the GPvar edit array // Tables: foreach ($this->editconf as $table => $conf) { if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) { // Traverse the keys/comments of each table (keys can be a commalist of uids) foreach ($conf as $cKey => $command) { if ($command == 'edit' || $command == 'new') { // Get the ids: $ids = GeneralUtility::trimExplode(',', $cKey, true); // Traverse the ids: foreach ($ids as $theUid) { // Don't save this document title in the document selector if the document is new. if ($command === 'new') { $this->dontStoreDocumentRef = 1; } /** @var TcaDatabaseRecord $formDataGroup */ $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class); /** @var FormDataCompiler $formDataCompiler */ $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup); /** @var NodeFactory $nodeFactory */ $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class); try { // Reset viewId - it should hold data of last entry only $this->viewId = 0; $this->viewId_addParams = ''; $formDataCompilerInput = ['tableName' => $table, 'vanillaUid' => (int) $theUid, 'command' => $command, 'returnUrl' => $this->R_URI]; if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) { $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table]; } $formData = $formDataCompiler->compile($formDataCompilerInput); // Set this->viewId if possible if ($command === 'new' && $table !== 'pages' && !empty($formData['parentPageRow']['uid'])) { $this->viewId = $formData['parentPageRow']['uid']; } else { if ($table == 'pages') { $this->viewId = $formData['databaseRow']['uid']; } elseif (!empty($formData['parentPageRow']['uid'])) { $this->viewId = $formData['parentPageRow']['uid']; // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero! if (!empty($formData['processedTca']['ctrl']['languageField']) && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']]) && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0) { $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0]; } } } // Determine if delete button can be shown $deleteAccess = false; if ($command === 'edit') { $permission = $formData['userPermissionOnPage']; if ($formData['tableName'] === 'pages') { $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false; } else { $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false; } } // Display "is-locked" message: if ($command === 'edit') { $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']); if ($lockInfo) { /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */ $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING); /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */ $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); /** @var $defaultFlashMessageQueue FlashMessageQueue */ $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); $defaultFlashMessageQueue->enqueue($flashMessage); } } // Record title if (!$this->storeTitle) { $this->storeTitle = $this->recTitle ? htmlspecialchars($this->recTitle) : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true); } $this->elementsData[] = array('table' => $table, 'uid' => $formData['databaseRow']['uid'], 'pid' => $formData['databaseRow']['pid'], 'cmd' => $command, 'deleteAccess' => $deleteAccess); if ($command !== 'new') { BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0); } // Set list if only specific fields should be rendered. This will trigger // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer if ($this->columnsOnly) { if (is_array($this->columnsOnly)) { $formData['fieldListToRender'] = $this->columnsOnly[$table]; } else { $formData['fieldListToRender'] = $this->columnsOnly; } } $formData['renderType'] = 'outerWrapContainer'; $formResult = $nodeFactory->create($formData)->render(); $html = $formResult['html']; $formResult['html'] = ''; $formResult['doSaveFieldName'] = 'doSave'; // @todo: Put all the stuff into FormEngine as final "compiler" class // @todo: This is done here for now to not rewrite JStop() // @todo: and printNeededJSFunctions() now $this->formResultCompiler->mergeResult($formResult); // Seems the pid is set as hidden field (again) at end?! if ($command == 'new') { // @todo: looks ugly $html .= LF . '<input type="hidden"' . ' name="data[' . $table . '][' . $formData['databaseRow']['uid'] . '][pid]"' . ' value="' . $formData['databaseRow']['pid'] . '" />'; $this->newC++; } $editForm .= $html; } catch (AccessDeniedException $e) { $this->errorC++; // Try to fetch error message from "recordInternals" be user object // @todo: This construct should be logged and localized and de-uglified $message = $beUser->errorMsg; if (empty($message)) { // Create message from exception. $message = $e->getMessage() . ' ' . $e->getCode(); } $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', true) . '<br /><br />' . htmlspecialchars($message) . '<br /><br />'; } } // End of for each uid } } } } return $editForm; }
/** * Determine the configuration and the type of a record selector. * This is a helper method for inline / IRRE handling * * @param array $conf TCA configuration of the parent(!) field * @param string $field Field name * @return array Associative array with the keys 'PA' and 'type', both are FALSE if the selector was not valid. * @internal */ public static function getInlinePossibleRecordsSelectorConfig($conf, $field = '') { $foreign_table = $conf['foreign_table']; $foreign_selector = $conf['foreign_selector']; $PA = false; $type = false; $table = false; $selector = false; if ($field) { $PA = array(); $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field]; if ($PA['fieldConf'] && $conf['foreign_selector_fieldTcaOverride']) { ArrayUtility::mergeRecursiveWithOverrule($PA['fieldConf'], $conf['foreign_selector_fieldTcaOverride']); } $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $field); $config = $PA['fieldConf']['config']; // Determine type of Selector: $type = static::getInlinePossibleRecordsSelectorType($config); // Return table on this level: $table = $type === 'select' ? $config['foreign_table'] : $config['allowed']; // Return type of the selector if foreign_selector is defined and points to the same field as in $field: if ($foreign_selector && $foreign_selector == $field && $type) { $selector = $type; } } return array('PA' => $PA, 'type' => $type, 'table' => $table, 'selector' => $selector); }
/** * Entry method * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $backendUser = $this->getBackendUserAuthentication(); $languageService = $this->getLanguageService(); $resultArray = $this->initializeResultArray(); $table = $this->data['tableName']; $row = $this->data['databaseRow']; $fieldName = $this->data['fieldName']; // @todo: it should be safe at this point, this array exists ... if (!is_array($this->data['processedTca']['columns'][$fieldName])) { return $resultArray; } $parameterArray = array(); $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName]; $isOverlay = false; // This field decides whether the current record is an overlay (as opposed to being a standalone record) // Based on this decision we need to trigger field exclusion or special rendering (like readOnly) if (isset($this->data['processedTca']['ctrl']['transOrigPointerField']) && is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']]) && is_array($row[$this->data['processedTca']['ctrl']['transOrigPointerField']]) && $row[$this->data['processedTca']['ctrl']['transOrigPointerField']][0] > 0) { $isOverlay = true; } // A couple of early returns in case the field should not be rendered // Check if this field is configured and editable according to exclude fields and other configuration if ($parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName) || $parameterArray['fieldConf']['config']['type'] === 'passthrough' || !$backendUser->isRTE() && $parameterArray['fieldConf']['config']['showIfRTE'] || $isOverlay && !$parameterArray['fieldConf']['l10n_display'] && $parameterArray['fieldConf']['l10n_mode'] === 'exclude' || $isOverlay && $this->data['localizationMode'] && $this->data['localizationMode'] !== $parameterArray['fieldConf']['l10n_cat'] || $this->inlineFieldShouldBeSkipped()) { return $resultArray; } $parameterArray['fieldTSConfig'] = []; if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']) && is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])) { $parameterArray['fieldTSConfig'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']; } if ($parameterArray['fieldTSConfig']['disabled']) { return $resultArray; } // Override fieldConf by fieldTSconfig: $parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']); $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'; $parameterArray['itemFormElID'] = 'data_' . $table . '_' . $row['uid'] . '_' . $fieldName; $newElementBaseName = $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'; // The value to show in the form field. $parameterArray['itemFormElValue'] = $row[$fieldName]; // Set field to read-only if configured for translated records to show default language content as readonly if ($parameterArray['fieldConf']['l10n_display'] && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly') && $isOverlay) { $parameterArray['fieldConf']['config']['readOnly'] = true; $parameterArray['itemFormElValue'] = $this->data['defaultLanguageRow'][$fieldName]; } if (strpos($this->data['processedTca']['ctrl']['type'], ':') === false) { $typeField = $this->data['processedTca']['ctrl']['type']; } else { $typeField = substr($this->data['processedTca']['ctrl']['type'], 0, strpos($this->data['processedTca']['ctrl']['type'], ':')); } // Create a JavaScript code line which will ask the user to save/update the form due to changing the element. // This is used for eg. "type" fields and others configured with "requestUpdate" if (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField || !empty($this->data['processedTca']['ctrl']['requestUpdate']) && GeneralUtility::inList(str_replace(' ', '', $this->data['processedTca']['ctrl']['requestUpdate']), $fieldName)) { if ($backendUser->jsConfirmation(JsConfirmation::TYPE_CHANGE)) { $alertMsgOnChange = 'top.TYPO3.Modal.confirm(TBE_EDITOR.labels.refreshRequired.title, TBE_EDITOR.labels.refreshRequired.content).on("button.clicked", function(e) { if (e.target.name == "ok" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); });'; } else { $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };'; } } else { $alertMsgOnChange = ''; } // JavaScript code for event handlers: $parameterArray['fieldChangeFunc'] = array(); $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ',' . GeneralUtility::quoteJSvalue($fieldName) . ',' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ');'; $parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange; // If this is the child of an inline type and it is the field creating the label if ($this->isInlineChildAndLabelField($table, $fieldName)) { /** @var InlineStackProcessor $inlineStackProcessor */ $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class); $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']); $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $inlineObjectId = implode('-', array($inlineDomObjectId, $table, $row['uid'])); $parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue($inlineObjectId) . ');'; } // Based on the type of the item, call a render function on a child element $options = $this->data; $options['parameterArray'] = $parameterArray; $options['elementBaseName'] = $newElementBaseName; if (!empty($parameterArray['fieldConf']['config']['renderType'])) { $options['renderType'] = $parameterArray['fieldConf']['config']['renderType']; } else { // Fallback to type if no renderType is given $options['renderType'] = $parameterArray['fieldConf']['config']['type']; } $resultArray = $this->nodeFactory->create($options)->render(); // If output is empty stop further processing. // This means there was internal processing only and we don't need to add additional information if (empty($resultArray['html'])) { return $resultArray; } $html = $resultArray['html']; // @todo: the language handling, the null and the placeholder stuff should be embedded in the single // @todo: element classes. Basically, this method should return here and have the element classes // @todo: decide on language stuff and other wraps already. // Add language + diff $renderLanguageDiff = true; if ($parameterArray['fieldConf']['l10n_display'] && (GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'hideDiff') || GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly'))) { $renderLanguageDiff = false; } if ($renderLanguageDiff) { $html = $this->renderDefaultLanguageContent($table, $fieldName, $row, $html); $html = $this->renderDefaultLanguageDiff($table, $fieldName, $row, $html); } $fieldItemClasses = array('t3js-formengine-field-item'); // NULL value and placeholder handling $nullControlNameAttribute = ' name="' . htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']') . '"'; if (!empty($parameterArray['fieldConf']['config']['eval']) && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null') && (empty($parameterArray['fieldConf']['config']['mode']) || $parameterArray['fieldConf']['config']['mode'] !== 'useOrOverridePlaceholder')) { // This field has eval=null set, but has no useOverridePlaceholder defined. // Goal is to have a field that can distinct between NULL and empty string in the database. // A checkbox and an additional hidden field will be created, both with the same name // and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual // input field will be written to the database. If the checkbox is not set, the hidden field // transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the // DataHandler at an early point in processing, so NULL will be written to DB as field value. // If the value of the field *is* NULL at the moment, an additional class is set // @todo: This does not work well at the moment, but is kept for now. see input_14 of ext:styleguide as example $checked = ' checked="checked"'; if ($this->data['databaseRow'][$fieldName] === null) { $fieldItemClasses[] = 'disabled'; $checked = ''; } $formElementName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'; $onChange = htmlspecialchars('typo3form.fieldSetNull(' . GeneralUtility::quoteJSvalue($formElementName) . ', !this.checked)'); $nullValueWrap = array(); $nullValueWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">'; $nullValueWrap[] = '<div class="t3-form-field-disable"></div>'; $nullValueWrap[] = '<div class="checkbox">'; $nullValueWrap[] = '<label>'; $nullValueWrap[] = '<input type="hidden"' . $nullControlNameAttribute . ' value="0" />'; $nullValueWrap[] = '<input type="checkbox"' . $nullControlNameAttribute . ' value="1" onchange="' . $onChange . '"' . $checked . ' /> '; $nullValueWrap[] = '</label>'; $nullValueWrap[] = $html; $nullValueWrap[] = '</div>'; $nullValueWrap[] = '</div>'; $html = implode(LF, $nullValueWrap); } elseif (isset($parameterArray['fieldConf']['config']['mode']) && $parameterArray['fieldConf']['config']['mode'] === 'useOrOverridePlaceholder') { // This field has useOverridePlaceholder set. // Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a // local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata, // the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string // or an empty string is then written to the field of sys_file_reference. // The situation is similar to the NULL handling above, but additionally a "default" value should be shown. // To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name // to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as // value in control[active], meaning the overridden value should be used. // Additionally to the casual input field, a second field is added containing the "placeholder" value. This // field has no name attribute and is not transferred at all. Those two are then hidden / shown depending // on the state of the above checkbox in via JS. $placeholder = empty($parameterArray['fieldConf']['config']['placeholder']) ? '' : $parameterArray['fieldConf']['config']['placeholder']; $onChange = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', !this.checked)'; $checked = $parameterArray['itemFormElValue'] === null ? '' : ' checked="checked"'; $resultArray['additionalJavaScriptPost'][] = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', ' . ($checked ? 'false' : 'true') . ');'; // Renders an input or textarea field depending on type of "parent" $options = array(); $options['databaseRow'] = array(); $options['table'] = ''; $options['parameterArray'] = $parameterArray; $options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30); $options['renderType'] = 'none'; $noneElementResult = $this->nodeFactory->create($options)->render(); $noneElementHtml = $noneElementResult['html']; $placeholderWrap = array(); $placeholderWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">'; $placeholderWrap[] = '<div class="t3-form-field-disable"></div>'; $placeholderWrap[] = '<div class="checkbox">'; $placeholderWrap[] = '<label>'; $placeholderWrap[] = '<input type="hidden"' . $nullControlNameAttribute . ' value="0" />'; $placeholderWrap[] = '<input type="checkbox"' . $nullControlNameAttribute . ' value="1" id="tce-forms-textfield-use-override-' . $fieldName . '-' . $row['uid'] . '" onchange="' . htmlspecialchars($onChange) . '"' . $checked . ' />'; $placeholderWrap[] = sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.placeholder.override'), BackendUtility::getRecordTitlePrep($placeholder, 20)); $placeholderWrap[] = '</label>'; $placeholderWrap[] = '</div>'; $placeholderWrap[] = '<div class="t3js-formengine-placeholder-placeholder">'; $placeholderWrap[] = $noneElementHtml; $placeholderWrap[] = '</div>'; $placeholderWrap[] = '<div class="t3js-formengine-placeholder-formfield">'; $placeholderWrap[] = $html; $placeholderWrap[] = '</div>'; $placeholderWrap[] = '</div>'; $html = implode(LF, $placeholderWrap); } elseif ($parameterArray['fieldConf']['config']['type'] !== 'user' || empty($parameterArray['fieldConf']['config']['noTableWrapping'])) { // Add a casual wrap if the field is not of type user with no wrap requested. $standardWrap = array(); $standardWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">'; $standardWrap[] = '<div class="t3-form-field-disable"></div>'; $standardWrap[] = $html; $standardWrap[] = '</div>'; $html = implode(LF, $standardWrap); } $resultArray['html'] = $html; return $resultArray; }
/** * Get a selector as used for the select type, to select from all available * records and to create a relation to the embedding record (e.g. like MM). * * @param array $selItems Array of all possible records * @param array $conf TCA configuration of the parent(!) field * @param array $PA An array with additional configuration options * @param array $uniqueIds The uids that have already been used and should be unique * @return string A HTML <select> box with all possible records */ protected function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds = array()) { $foreign_table = $conf['foreign_table']; $foreign_selector = $conf['foreign_selector']; $PA = array(); $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]; $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $foreign_selector); $config = $PA['fieldConf']['config']; $item = ''; // @todo $disabled is not present - should be read from config? $disabled = false; if (!$disabled) { $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); // Create option tags: $opt = array(); foreach ($selItems as $p) { if (!in_array($p[1], $uniqueIds)) { $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>'; } } // Put together the selector box: $itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ''; $size = (int) $conf['size']; $size = $conf['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $conf['autoSizeMax']) : $size; $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $conf['foreign_table']) . ')'; $item = ' <select id="' . $nameObject . '-' . $conf['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($onChange) . '"' . $PA['onFocus'] . $itemListStyle . ($conf['foreign_unique'] ? ' isunique="isunique"' : '') . '> ' . implode('', $opt) . ' </select>'; if ($size <= 1) { // Add a "Create new relation" link for adding new relations // This is necessary, if the size of the selector is "1" or if // there is only one record item in the select-box, that is selected by default // The selector-box creates a new relation on using an onChange event (see some line above) if (!empty($conf['appearance']['createNewRelationLinkTitle'])) { $createNewRelationText = $this->getLanguageService()->sL($conf['appearance']['createNewRelationLinkTitle'], true); } else { $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true); } $item .= ' <span class="input-group-btn"> <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '"> ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . $createNewRelationText . ' </a> </span>'; } else { $item .= ' <span class="input-group-btn btn"></span>'; } // Wrap the selector and add a spacer to the bottom $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>'; } return $item; }
/** * Creates a checkbox list (renderMode = "checkbox") * * @param string $table See getSingleField_typeSelect() * @param string $field See getSingleField_typeSelect() * @param array $row See getSingleField_typeSelect() * @param array $parameterArray See getSingleField_typeSelect() * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience) * @param array $selItems Items available for selection * @param string $noMatchingLabel Label for no-matching-value * @return string The HTML code for the item */ protected function getSingleField_typeSelect_checkbox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) { if (empty($selItems)) { return ''; } // Get values in an array (and make unique, which is fine because there can be no duplicates anyway): $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($parameterArray['itemFormElValue'])); $output = ''; // Disabled $disabled = 0; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = 1; } // Traverse the Array of selector box items: $groups = array(); $currentGroup = 0; $c = 0; $sOnChange = ''; if (!$disabled) { $sOnChange = implode('', $parameterArray['fieldChangeFunc']); // Used to accumulate the JS needed to restore the original selection. foreach ($selItems as $p) { // Non-selectable element: if ($p[1] === '--div--') { $selIcon = ''; if (isset($p[2]) && $p[2] != 'empty-empty') { $selIcon = FormEngineUtility::getIconHtml($p[2]); } $currentGroup++; $groups[$currentGroup]['header'] = array('icon' => $selIcon, 'title' => htmlspecialchars($p[0])); } else { // Check if some help text is available // Since TYPO3 4.5 help text is expected to be an associative array // with two key, "title" and "description" // For the sake of backwards compatibility, we test if the help text // is a string and use it as a description (this could happen if items // are modified with an itemProcFunc) $hasHelp = FALSE; $help = ''; $helpArray = array(); if (!empty($p[3])) { $hasHelp = TRUE; if (is_array($p[3])) { $helpArray = $p[3]; } else { $helpArray['description'] = $p[3]; } } if ($hasHelp) { $help = BackendUtility::wrapInHelp('', '', '', $helpArray); } // Selected or not by default: $checked = 0; if (isset($itemArray[$p[1]])) { $checked = 1; unset($itemArray[$p[1]]); } // Build item array $groups[$currentGroup]['items'][] = array('id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)), 'name' => $parameterArray['itemFormElName'] . '[' . $c . ']', 'value' => $p[1], 'checked' => $checked, 'disabled' => $disabled, 'class' => '', 'icon' => !empty($p[2]) ? FormEngineUtility::getIconHtml($p[2]) : IconUtility::getSpriteIcon('empty-empty'), 'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE), 'help' => $help); $c++; } } } // Remaining values (invalid): if (!empty($itemArray) && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) { $currentGroup++; foreach ($itemArray as $theNoMatchValue => $temp) { // Build item array $groups[$currentGroup]['items'][] = array('id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)), 'name' => $parameterArray['itemFormElName'] . '[' . $c . ']', 'value' => $theNoMatchValue, 'checked' => 1, 'disabled' => $disabled, 'class' => 'danger', 'icon' => '', 'title' => htmlspecialchars(@sprintf($noMatchingLabel, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE), 'help' => ''); $c++; } } // Add an empty hidden field which will send a blank value if all items are unselected. $output .= '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="" />'; // Building the checkboxes foreach ($groups as $groupKey => $group) { $groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey; $output .= '<div class="panel panel-default">'; if (is_array($group['header'])) { $output .= ' <div class="panel-heading"> <a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="true" aria-controls="' . $groupId . '"> ' . $group['header']['icon'] . ' ' . $group['header']['title'] . ' </a> </div> '; } if (is_array($group['items']) && !empty($group['items'])) { $tableRows = ''; $checkGroup = array(); $uncheckGroup = array(); $resetGroup = array(); // Render rows foreach ($group['items'] as $item) { $tableRows .= ' <tr class="' . $item['class'] . '"> <td class="col-checkbox"> <input type="checkbox" id="' . $item['id'] . '" name="' . htmlspecialchars($item['name']) . '" value="' . htmlspecialchars($item['value']) . '" onclick="' . htmlspecialchars($sOnChange) . '" ' . ($item['checked'] ? ' checked=checked' : '') . ' ' . ($item['disabled'] ? ' disabled=disabled' : '') . ' ' . $parameterArray['onFocus'] . ' /> </td> <td class="col-icon"> <label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label> </td> <td class="col-title"> <label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label> </td> <td>' . $item['help'] . '</td> </tr> '; $checkGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=1;'; $uncheckGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=0;'; $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=' . $item['checked'] . ';'; } // Build toggle group checkbox $toggleGroupCheckbox = ''; if (!empty($resetGroup)) { $toggleGroupCheckbox = ' <input type="checkbox" class="checkbox" onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}"> '; } // Build reset group button $resetGroupBtn = ''; if (!empty($resetGroup)) { $resetGroupBtn = ' <a href="#" class="btn btn-default" onclick="' . implode('', $resetGroup) . ' return false;' . '"> ' . IconUtility::getSpriteIcon('actions-edit-undo', array('title' => htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')))) . ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . ' </a> '; } $output .= ' <div id="' . $groupId . '" class="panel-collapse collapse in" role="tabpanel"> <div class="table-fit"> <table class="table table-transparent table-hover"> <thead> <tr> <th class="col-checkbox">' . $toggleGroupCheckbox . '</th> <th class="col-icon"></th> <th class="text-right" colspan="2">' . $resetGroupBtn . '</th> </tr> </thead> <tbody>' . $tableRows . '</tbody> </table> </div> </div> '; } $output .= '</div>'; } return $output; }
/** * Rendering wizards for form fields. * * @param array $itemKinds Array with the real item in the first value, and an alternative item in the second value. * @param array $wizConf The "wizard" key from the config array for the field (from TCA) * @param string $table Table name * @param array $row The record array * @param string $field The field name * @param array $PA Additional configuration array. * @param string $itemName The field name * @param array $specConf Special configuration if available. * @param bool $RTE Whether the RTE could have been loaded. * @return string The new item value. */ protected function renderWizards($itemKinds, $wizConf, $table, $row, $field, $PA, $itemName, $specConf, $RTE = FALSE) { // Return not changed main item directly if wizards are disabled if (!is_array($wizConf) || $this->isWizardsDisabled()) { return $itemKinds[0]; } $languageService = $this->getLanguageService(); $fieldChangeFunc = $PA['fieldChangeFunc']; $item = $itemKinds[0]; $fName = '[' . $table . '][' . $row['uid'] . '][' . $field . ']'; $md5ID = 'ID' . GeneralUtility::shortmd5($itemName); $fieldConfig = $PA['fieldConf']['config']; $prefixOfFormElName = 'data[' . $table . '][' . $row['uid'] . '][' . $field . ']'; $flexFormPath = ''; if (GeneralUtility::isFirstPartOfStr($PA['itemFormElName'], $prefixOfFormElName)) { $flexFormPath = str_replace('][', '/', substr($PA['itemFormElName'], strlen($prefixOfFormElName) + 1, -1)); } // Manipulate the field name (to be the TRUE form field name) and remove // a suffix-value if the item is a selector box with renderMode "singlebox": $listFlag = '_list'; if ($PA['fieldConf']['config']['type'] == 'select') { // Single select situation: if ($PA['fieldConf']['config']['maxitems'] <= 1) { $listFlag = ''; } elseif ($PA['fieldConf']['config']['renderMode'] == 'singlebox') { $itemName .= '[]'; $listFlag = ''; } } // Contains wizard identifiers enabled for this record type, see "special configuration" docs $wizardsEnabledByType = $specConf['wizards']['parameters']; $buttonWizards = array(); $otherWizards = array(); foreach ($wizConf as $wizardIdentifier => $wizardConfiguration) { // If an identifier starts with "_", this is a configuration option like _POSITION and not a wizard if ($wizardIdentifier[0] === '_') { continue; } // Sanitize wizard type $wizardConfiguration['type'] = (string) $wizardConfiguration['type']; // Wizards can be shown based on selected "type" of record. If this is the case, the wizard configuration // is set to enableByTypeConfig = 1, and the wizardIdentifier is found in $wizardsEnabledByType $wizardIsEnabled = TRUE; if (isset($wizardConfiguration['enableByTypeConfig']) && (bool) $wizardConfiguration['enableByTypeConfig'] && (!is_array($wizardsEnabledByType) || !in_array($wizardIdentifier, $wizardsEnabledByType))) { $wizardIsEnabled = FALSE; } // Disable if wizard is for RTE fields only and the handled field is no RTE field or RTE can not be loaded if (isset($wizardConfiguration['RTEonly']) && (bool) $wizardConfiguration['RTEonly'] && !$RTE) { $wizardIsEnabled = FALSE; } // Disable if wizard is for not-new records only and we're handling a new record if (isset($wizardConfiguration['notNewRecords']) && $wizardConfiguration['notNewRecords'] && !MathUtility::canBeInterpretedAsInteger($row['uid'])) { $wizardIsEnabled = FALSE; } // Wizard types script, colorbox and popup must contain a module name configuration if (!isset($wizardConfiguration['module']['name']) && in_array($wizardConfiguration['type'], array('script', 'colorbox', 'popup'), TRUE)) { $wizardIsEnabled = FALSE; } if (!$wizardIsEnabled) { continue; } // Title / icon: $iTitle = htmlspecialchars($languageService->sL($wizardConfiguration['title'])); if (isset($wizardConfiguration['icon'])) { $icon = FormEngineUtility::getIconHtml($wizardConfiguration['icon'], $iTitle, $iTitle); } else { $icon = $iTitle; } switch ($wizardConfiguration['type']) { case 'userFunc': $params = array(); $params['fieldConfig'] = $fieldConfig; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->getReturnUrl(); $params['formName'] = 'editform'; $params['itemName'] = $itemName; $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js'); $params['fieldChangeFunc'] = $fieldChangeFunc; $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc)); $params['item'] =& $item; $params['icon'] = $icon; $params['iTitle'] = $iTitle; $params['wConf'] = $wizardConfiguration; $params['row'] = $row; $formEngineDummy = new FormEngine(); $otherWizards[] = GeneralUtility::callUserFunction($wizardConfiguration['userFunc'], $params, $formEngineDummy); break; case 'script': $params = array(); // Including the full fieldConfig from TCA may produce too long an URL if ($wizardIdentifier != 'RTE') { $params['fieldConfig'] = $fieldConfig; } $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->getReturnUrl(); // Resolving script filename and setting URL. $urlParameters = array(); if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) { $urlParameters = $wizardConfiguration['module']['urlParameters']; } $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, ''); $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params)); $buttonWizards[] = '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" onclick="this.blur(); return !TBE_EDITOR.isFormChanged();">' . $icon . '</a>'; break; case 'popup': $params = array(); $params['fieldConfig'] = $fieldConfig; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->getReturnUrl(); $params['formName'] = 'editform'; $params['itemName'] = $itemName; $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js'); $params['fieldChangeFunc'] = $fieldChangeFunc; $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc)); // Resolving script filename and setting URL. $urlParameters = array(); if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) { $urlParameters = $wizardConfiguration['module']['urlParameters']; } $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, ''); $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params)); $onlyIfSelectedJS = ''; if (isset($wizardConfiguration['popup_onlyOpenIfSelected']) && $wizardConfiguration['popup_onlyOpenIfSelected']) { $notSelectedText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.noSelItemForEdit'); $onlyIfSelectedJS = 'if (!TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . ')){' . 'alert(' . GeneralUtility::quoteJSvalue($notSelectedText) . ');' . 'return false;' . '}'; } $aOnClick = 'this.blur();' . $onlyIfSelectedJS . 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' . 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,200' . ')' . '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . '),' . GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' . GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) . ');' . 'vHWin.focus();' . 'return false;'; $buttonWizards[] = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>'; break; case 'colorbox': $params = array(); $params['fieldConfig'] = $fieldConfig; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->getReturnUrl(); $params['formName'] = 'editform'; $params['itemName'] = $itemName; $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js'); $params['fieldChangeFunc'] = $fieldChangeFunc; $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc)); // Resolving script filename and setting URL. $urlParameters = array(); if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) { $urlParameters = $wizardConfiguration['module']['urlParameters']; } $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, ''); $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params)); $aOnClick = 'this.blur();' . 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' . 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,200' . ')' . '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . '),' . GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' . GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) . ');' . 'vHWin.focus();' . 'return false;'; $otherWizards[] = '<a id="' . $md5ID . '" class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '"><span class="t3-icon fa fa-eyedropper"></span></a>'; break; case 'slider': $params = array(); $params['fieldConfig'] = $fieldConfig; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['itemName'] = $itemName; $params['fieldChangeFunc'] = $fieldChangeFunc; $params['wConf'] = $wizardConfiguration; $params['row'] = $row; /** @var ValueSliderWizard $wizard */ $wizard = GeneralUtility::makeInstance(ValueSliderWizard::class); $otherWizards[] = $wizard->renderWizard($params); break; case 'select': $fieldValue = array('config' => $wizardConfiguration); $TSconfig = FormEngineUtility::getTSconfigForTableRow($table, $row); $TSconfig[$field] = $TSconfig[$field]['wizards.'][$wizardIdentifier . '.']; $selItems = FormEngineUtility::addSelectOptionsToItemArray(FormEngineUtility::initItemArray($fieldValue), $fieldValue, $TSconfig, $field); // Process items by a user function: if (!empty($wizardConfiguration['itemsProcFunc'])) { $funcConfig = !empty($wizardConfiguration['itemsProcFunc.']) ? $wizardConfiguration['itemsProcFunc.'] : array(); $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class); $selItems = $dataPreprocessor->procItems($selItems, $funcConfig, $wizardConfiguration, $table, $row, $field); } $options = array(); $options[] = '<option>' . $iTitle . '</option>'; foreach ($selItems as $p) { $options[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>'; } if ($wizardConfiguration['mode'] == 'append') { $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value'; } elseif ($wizardConfiguration['mode'] == 'prepend') { $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value+=\'\'+this.options[this.selectedIndex].value'; } else { $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=this.options[this.selectedIndex].value'; } $otherWizards[] = '<select' . ' id="' . str_replace('.', '', uniqid('tceforms-select-', TRUE)) . '"' . ' class="form-control tceforms-select tceforms-wizardselect"' . ' name="_WIZARD' . $fName . '"' . ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"' . '>' . implode('', $options) . '</select>'; break; case 'suggest': if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) { break; } /** @var SuggestWizard $suggestWizard */ $suggestWizard = GeneralUtility::makeInstance(SuggestWizard::class); $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA); break; } // Hide the real form element? if (is_array($wizardConfiguration['hideParent']) || $wizardConfiguration['hideParent']) { // Setting the item to a hidden-field. $item = $itemKinds[1]; if (is_array($wizardConfiguration['hideParent'])) { $options = $this->globalOptions; $options['parameterArray'] = array('fieldConf' => array('config' => $wizardConfiguration['hideParent']), 'itemFormElValue' => $PA['itemFormElValue']); $options['renderType'] = 'none'; /** @var NodeFactory $nodeFactory */ $nodeFactory = $this->globalOptions['nodeFactory']; $noneElementResult = $nodeFactory->create($options)->render(); $item .= $noneElementResult['html']; } } } // For each rendered wizard, put them together around the item. if (!empty($buttonWizards) || !empty($otherWizards)) { if ($wizConf['_HIDDENFIELD']) { $item = $itemKinds[1]; } $innerContent = ''; if (!empty($buttonWizards)) { $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ? ' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>'; } $innerContent .= implode(' ', $otherWizards); // Position $classes = array('form-wizards-wrap'); if ($wizConf['_POSITION'] === 'left') { $classes[] = 'form-wizards-aside'; $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>'; } elseif ($wizConf['_POSITION'] === 'top') { $classes[] = 'form-wizards-top'; $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>'; } elseif ($wizConf['_POSITION'] === 'bottom') { $classes[] = 'form-wizards-bottom'; $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>'; } else { $classes[] = 'form-wizards-aside'; $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>'; } $item = ' <div class="' . implode(' ', $classes) . '"> ' . $innerContent . ' </div>'; } return $item; }
/** * Rendering wizards for form fields. * * @param array $itemKinds Array with the real item in the first value * @param array $wizConf The "wizards" key from the config array for the field (from TCA) * @param string $table Table name * @param array $row The record array * @param string $field The field name * @param array $PA Additional configuration array. * @param string $itemName The field name * @param array $specConf Special configuration if available. * @param bool $RTE Whether the RTE could have been loaded. * * @return string The new item value. * @throws \InvalidArgumentException */ protected function renderWizards($itemKinds, $wizConf, $table, $row, $field, $PA, $itemName, $specConf, $RTE = false) { // Return not changed main item directly if wizards are disabled if (!is_array($wizConf) || $this->isWizardsDisabled()) { return $itemKinds[0]; } $languageService = $this->getLanguageService(); $fieldChangeFunc = $PA['fieldChangeFunc']; $item = $itemKinds[0]; $md5ID = 'ID' . GeneralUtility::shortMD5($itemName); $prefixOfFormElName = 'data[' . $table . '][' . $row['uid'] . '][' . $field . ']'; $flexFormPath = ''; if (GeneralUtility::isFirstPartOfStr($PA['itemFormElName'], $prefixOfFormElName)) { $flexFormPath = str_replace('][', '/', substr($PA['itemFormElName'], strlen($prefixOfFormElName) + 1, -1)); } // Add a suffix-value if the item is a selector box with renderType "selectSingleBox": if ($PA['fieldConf']['config']['type'] === 'select' && (int) $PA['fieldConf']['config']['maxitems'] > 1 && $PA['fieldConf']['config']['renderType'] === 'selectSingleBox') { $itemName .= '[]'; } // Contains wizard identifiers enabled for this record type, see "special configuration" docs $wizardsEnabledByType = $specConf['wizards']['parameters']; $buttonWizards = []; $otherWizards = []; foreach ($wizConf as $wizardIdentifier => $wizardConfiguration) { if (!isset($wizardConfiguration['module']['name']) && isset($wizardConfiguration['script'])) { throw new \InvalidArgumentException('The way registering a wizard in TCA has changed in 6.2 and was removed in CMS 7. ' . 'Please set module[name]=module_name instead of using script=path/to/script.php in your TCA. ', 1437750231); } // If an identifier starts with "_", this is a configuration option like _POSITION and not a wizard if ($wizardIdentifier[0] === '_') { continue; } // Sanitize wizard type $wizardConfiguration['type'] = (string) $wizardConfiguration['type']; // Wizards can be shown based on selected "type" of record. If this is the case, the wizard configuration // is set to enableByTypeConfig = 1, and the wizardIdentifier is found in $wizardsEnabledByType $wizardIsEnabled = true; if (isset($wizardConfiguration['enableByTypeConfig']) && (bool) $wizardConfiguration['enableByTypeConfig'] && (!is_array($wizardsEnabledByType) || !in_array($wizardIdentifier, $wizardsEnabledByType))) { $wizardIsEnabled = false; } // Disable if wizard is for RTE fields only and the handled field is no RTE field or RTE can not be loaded if (isset($wizardConfiguration['RTEonly']) && (bool) $wizardConfiguration['RTEonly'] && !$RTE) { $wizardIsEnabled = false; } // Disable if wizard is for not-new records only and we're handling a new record if (isset($wizardConfiguration['notNewRecords']) && $wizardConfiguration['notNewRecords'] && !MathUtility::canBeInterpretedAsInteger($row['uid'])) { $wizardIsEnabled = false; } // Wizard types script, colorbox and popup must contain a module name configuration if (!isset($wizardConfiguration['module']['name']) && in_array($wizardConfiguration['type'], ['script', 'colorbox', 'popup'], true)) { $wizardIsEnabled = false; } if (!$wizardIsEnabled) { continue; } // Title / icon: $iTitle = htmlspecialchars($languageService->sL($wizardConfiguration['title'])); if (isset($wizardConfiguration['icon'])) { $icon = FormEngineUtility::getIconHtml($wizardConfiguration['icon'], $iTitle, $iTitle); } else { $icon = $iTitle; } switch ($wizardConfiguration['type']) { case 'userFunc': $params = []; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->data['returnUrl']; $params['formName'] = 'editform'; $params['itemName'] = $itemName; $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js'); $params['fieldChangeFunc'] = $fieldChangeFunc; $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc)); $params['item'] =& $item; $params['icon'] = $icon; $params['iTitle'] = $iTitle; $params['wConf'] = $wizardConfiguration; $params['row'] = $row; $otherWizards[] = GeneralUtility::callUserFunction($wizardConfiguration['userFunc'], $params, $this); break; case 'script': $params = []; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->data['returnUrl']; // Resolving script filename and setting URL. $urlParameters = []; if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) { $urlParameters = $wizardConfiguration['module']['urlParameters']; } $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, ''); $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', ['P' => $params]); $buttonWizards[] = '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" onclick="this.blur(); return !TBE_EDITOR.isFormChanged();">' . $icon . '</a>'; break; case 'popup': $params = []; $params['params'] = $wizardConfiguration['params']; $params['exampleImg'] = $wizardConfiguration['exampleImg']; $params['table'] = $table; $params['uid'] = $row['uid']; $params['pid'] = $row['pid']; $params['field'] = $field; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['returnUrl'] = $this->data['returnUrl']; $params['formName'] = 'editform'; $params['itemName'] = $itemName; $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js'); $params['fieldChangeFunc'] = $fieldChangeFunc; $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc)); // Resolving script filename and setting URL. $urlParameters = []; if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) { $urlParameters = $wizardConfiguration['module']['urlParameters']; } $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, ''); $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', ['P' => $params]); $onlyIfSelectedJS = ''; if (isset($wizardConfiguration['popup_onlyOpenIfSelected']) && $wizardConfiguration['popup_onlyOpenIfSelected']) { $notSelectedText = $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.noSelItemForEdit'); $onlyIfSelectedJS = 'if (!TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . ')){' . 'alert(' . GeneralUtility::quoteJSvalue($notSelectedText) . ');' . 'return false;' . '}'; } $aOnClick = 'this.blur();' . $onlyIfSelectedJS . 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' . 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,300' . ')' . '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . '),' . GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' . GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) . ');' . 'vHWin.focus();' . 'return false;'; $buttonWizards[] = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>'; break; case 'slider': $params = []; $params['fieldConfig'] = $PA['fieldConf']['config']; $params['field'] = $field; $params['table'] = $table; $params['flexFormPath'] = $flexFormPath; $params['md5ID'] = $md5ID; $params['itemName'] = $itemName; $params['wConf'] = $wizardConfiguration; $params['row'] = $row; /** @var ValueSliderWizard $wizard */ $wizard = GeneralUtility::makeInstance(ValueSliderWizard::class); $otherWizards[] = $wizard->renderWizard($params); break; case 'select': // The select wizard is a select drop down added to the main element. It provides all the functionality // that select items can do for us, so we process this element via data processing. // @todo: This should be embedded in an own provider called in the main data group to not handle this on the fly here // Select wizard page TS can be set in TCEFORM."table"."field".wizards."wizardName" $pageTsConfig = []; if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.']) && is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'])) { $pageTsConfig['TCEFORM.']['dummySelectWizard.'][$wizardIdentifier . '.'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.']; } $selectWizardDataInput = ['tableName' => 'dummySelectWizard', 'command' => 'edit', 'pageTsConfig' => $pageTsConfig, 'processedTca' => ['ctrl' => [], 'columns' => [$wizardIdentifier => ['type' => 'select', 'renderType' => 'selectSingle', 'config' => $wizardConfiguration]]]]; /** @var OnTheFly $formDataGroup */ $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class); $formDataGroup->setProviderList([TcaSelectItems::class]); /** @var FormDataCompiler $formDataCompiler */ $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup); $compilerResult = $formDataCompiler->compile($selectWizardDataInput); $selectWizardItems = $compilerResult['processedTca']['columns'][$wizardIdentifier]['config']['items']; $options = []; $options[] = '<option>' . $iTitle . '</option>'; foreach ($selectWizardItems as $selectWizardItem) { $options[] = '<option value="' . htmlspecialchars($selectWizardItem[1]) . '">' . htmlspecialchars($selectWizardItem[0]) . '</option>'; } if ($wizardConfiguration['mode'] == 'append') { $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value'; } elseif ($wizardConfiguration['mode'] == 'prepend') { $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value+=\'\'+this.options[this.selectedIndex].value'; } else { $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=this.options[this.selectedIndex].value'; } $otherWizards[] = '<select' . ' id="' . StringUtility::getUniqueId('tceforms-select-') . '"' . ' class="form-control tceforms-select tceforms-wizardselect"' . ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"' . '>' . implode('', $options) . '</select>'; break; case 'suggest': if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) { break; } // The suggest wizard needs to know if we're in flex form scope to use the dataStructureIdentifier. // If so, add the processedTca of the flex config as wizard argument. $flexFormConfig = []; if ($this->data['processedTca']['columns'][$field]['config']['type'] === 'flex') { $flexFormConfig = $this->data['processedTca']['columns'][$field]; } /** @var SuggestWizard $suggestWizard */ $suggestWizard = GeneralUtility::makeInstance(SuggestWizard::class); $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA, $flexFormConfig); break; } } // For each rendered wizard, put them together around the item. if (!empty($buttonWizards) || !empty($otherWizards)) { $innerContent = ''; if (!empty($buttonWizards)) { $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ? ' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>'; } $innerContent .= implode(' ', $otherWizards); // Position $classes = ['form-wizards-wrap']; if ($wizConf['_POSITION'] === 'left') { $classes[] = 'form-wizards-aside'; $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>'; } elseif ($wizConf['_POSITION'] === 'top') { $classes[] = 'form-wizards-top'; $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>'; } elseif ($wizConf['_POSITION'] === 'bottom') { $classes[] = 'form-wizards-bottom'; $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>'; } else { $classes[] = 'form-wizards-aside'; $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>'; } $item = ' <div class="' . implode(' ', $classes) . '"> ' . $innerContent . ' </div>'; } return $item; }
/** * Do processing of data, submitting it to TCEmain. * * @return void */ public function processData() { $beUser = $this->getBackendUser(); // GPvars specifically for processing: $control = GeneralUtility::_GP('control'); $this->data = GeneralUtility::_GP('data'); $this->cmd = GeneralUtility::_GP('cmd'); $this->mirror = GeneralUtility::_GP('mirror'); $this->cacheCmd = GeneralUtility::_GP('cacheCmd'); $this->redirect = GeneralUtility::_GP('redirect'); $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId'); $this->vC = GeneralUtility::_GP('vC'); // See tce_db.php for relevate options here: // Only options related to $this->data submission are included here. /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */ $tce = GeneralUtility::makeInstance(DataHandler::class); $tce->stripslashes_values = FALSE; if (!empty($control)) { $tce->setControl($control); } if (isset($_POST['_translation_savedok_x'])) { $tce->updateModeL10NdiffData = 'FORCE_FFUPD'; } if (isset($_POST['_translation_savedokclear_x'])) { $tce->updateModeL10NdiffData = 'FORCE_FFUPD'; $tce->updateModeL10NdiffDataClear = TRUE; } // Setting default values specific for the user: $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults'); if (is_array($TCAdefaultOverride)) { $tce->setDefaultsFromUserTS($TCAdefaultOverride); } // Setting internal vars: if ($beUser->uc['neverHideAtCopy']) { $tce->neverHideAtCopy = 1; } // Loading TCEmain with data: $tce->start($this->data, $this->cmd); if (is_array($this->mirror)) { $tce->setMirror($this->mirror); } // Checking referer / executing $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER')); $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'); if ($httpHost != $refInfo['host'] && $this->vC != $beUser->veriCode() && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) { $tce->log('', 0, 0, 0, 1, 'Referer host \'%s\' and server host \'%s\' did not match and veriCode was not valid either!', 1, array($refInfo['host'], $httpHost)); debug('Error: Referer host did not match with server host.'); } else { // Perform the saving operation with TCEmain: $tce->process_uploads($_FILES); $tce->process_datamap(); $tce->process_cmdmap(); // If pages are being edited, we set an instruction about updating the page tree after this operation. if ($tce->pagetreeNeedsRefresh && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))) { BackendUtility::setUpdateSignal('updatePageTree'); } // If there was saved any new items, load them: if (!empty($tce->substNEWwithIDs_table)) { // save the expanded/collapsed states for new inline records, if any FormEngineUtility::updateInlineView($this->uc, $tce); $newEditConf = array(); foreach ($this->editconf as $tableName => $tableCmds) { $keys = array_keys($tce->substNEWwithIDs_table, $tableName); if (!empty($keys)) { foreach ($keys as $key) { $editId = $tce->substNEWwithIDs[$key]; // Check if the $editId isn't a child record of an IRRE action if (!(is_array($tce->newRelatedIDs[$tableName]) && in_array($editId, $tce->newRelatedIDs[$tableName]))) { // Translate new id to the workspace version: if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord($beUser->workspace, $tableName, $editId, 'uid')) { $editId = $versionRec['uid']; } $newEditConf[$tableName][$editId] = 'edit'; } // Traverse all new records and forge the content of ->editconf so we can continue to EDIT these records! if ($tableName == 'pages' && $this->retUrl != BackendUtility::getModuleUrl('dummy') && $this->returnNewPageId) { $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key]; } } } else { $newEditConf[$tableName] = $tableCmds; } } // Resetting editconf if newEditConf has values: if (!empty($newEditConf)) { $this->editconf = $newEditConf; } // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed. $this->R_URL_getvars['edit'] = $this->editconf; // Unsetting default values since we don't need them anymore. unset($this->R_URL_getvars['defVals']); // Re-compile the store* values since editconf changed... $this->compileStoreDat(); } // See if any records was auto-created as new versions? if (!empty($tce->autoVersionIdMap)) { $this->fixWSversioningInEditConf($tce->autoVersionIdMap); } // If a document is saved and a new one is created right after. if (isset($_POST['_savedoknew_x']) && is_array($this->editconf)) { // Finding the current table: reset($this->editconf); $nTable = key($this->editconf); // Finding the first id, getting the records pid+uid reset($this->editconf[$nTable]); $nUid = key($this->editconf[$nTable]); $nRec = BackendUtility::getRecord($nTable, $nUid, 'pid,uid'); // Setting a blank editconf array for a new record: $this->editconf = array(); if ($this->getNewIconMode($nTable) == 'top') { $this->editconf[$nTable][$nRec['pid']] = 'new'; } else { $this->editconf[$nTable][-$nRec['uid']] = 'new'; } // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed. $this->R_URL_getvars['edit'] = $this->editconf; // Re-compile the store* values since editconf changed... $this->compileStoreDat(); } // If a preview is requested if (isset($_POST['_savedokview_x'])) { // Get the first table and id of the data array from DataHandler $table = reset(array_keys($this->data)); $id = reset(array_keys($this->data[$table])); if (!MathUtility::canBeInterpretedAsInteger($id)) { $id = $tce->substNEWwithIDs[$id]; } // Store this information for later use $this->previewData['table'] = $table; $this->previewData['id'] = $id; } $tce->printLogErrorMessages(isset($_POST['_saveandclosedok_x']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars)); } // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED // because if not, we just get that element re-listed as new. And we don't want that! if (isset($_POST['_saveandclosedok_x']) || isset($_POST['_translation_savedok_x']) || $this->closeDoc < 0) { $this->closeDocument(abs($this->closeDoc)); } }
/** * Get type selector * * @return string */ protected function getTypeSelectHtml() { $content = ''; // find all available doktypes for the current user $types = $GLOBALS['PAGES_TYPES']; unset($types['default']); $types = array_keys($types); $types[] = PageRepository::DOKTYPE_DEFAULT; if (!$this->getBackendUser()->isAdmin() && isset($this->getBackendUser()->groupData['pagetypes_select'])) { $types = GeneralUtility::trimExplode(',', $this->getBackendUser()->groupData['pagetypes_select'], true); } $removeItems = isset($this->pagesTsConfig['doktype.']['removeItems']) ? GeneralUtility::trimExplode(',', $this->pagesTsConfig['doktype.']['removeItems'], true) : array(); $allowedDoktypes = array_diff($types, $removeItems); // fetch all doktypes in the TCA $availableDoktypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items']; // sort by group and allowedDoktypes $groupedData = array(); foreach ($availableDoktypes as $doktypeData) { // if it is a group, save the group label for the children underneath if ($doktypeData[1] == '--div--') { $groupLabel = $doktypeData[0]; } else { if (in_array($doktypeData[1], $allowedDoktypes)) { $groupedData[$groupLabel][] = $doktypeData; } } } // render the HTML foreach ($groupedData as $groupLabel => $items) { $groupContent = ''; foreach ($items as $item) { $label = $this->getLanguageService()->sL($item[0], true); $value = $item[1]; $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $label, $label) : ''; $groupContent .= '<option value="' . htmlspecialchars($value) . '" data-icon="' . htmlspecialchars($icon) . '">' . $label . '</option>'; } $groupLabel = $this->getLanguageService()->sL($groupLabel, true); $content .= '<optgroup label="' . $groupLabel . '">' . $groupContent . '</optgroup>'; } return $content; }
/** * Creates a multiple-selector box (two boxes, side-by-side) * * @param string $table See getSingleField_typeSelect() * @param string $field See getSingleField_typeSelect() * @param array $row See getSingleField_typeSelect() * @param array $parameterArray See getSingleField_typeSelect() * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience) * @param array $selItems Items available for selection * @param string $noMatchingLabel Label for no-matching-value * @return string The HTML code for the item */ protected function getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) { $languageService = $this->getLanguageService(); $item = ''; $disabled = ''; if ($this->isGlobalReadonly() || $config['readOnly']) { $disabled = ' disabled="disabled"'; } // Setting this hidden field (as a flag that JavaScript can read out) if (!$disabled) { $item .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />'; } // Set max and min items: $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0); if (!$maxitems) { $maxitems = 100000; } // Get "removeItems": $removeItems = GeneralUtility::trimExplode(',', $parameterArray['fieldTSConfig']['removeItems'], TRUE); // Get the array with selected items: $itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], TRUE); // Possibly filter some items: $itemArray = ArrayUtility::keepItemsInArray($itemArray, $parameterArray['fieldTSConfig']['keepItems'], function ($value) { $parts = explode('|', $value, 2); return rawurldecode($parts[0]); }); // Perform modification of the selected items array: foreach ($itemArray as $tk => $tv) { $tvP = explode('|', $tv, 2); $evalValue = $tvP[0]; $isRemoved = in_array($evalValue, $removeItems) || $config['type'] == 'select' && $config['authMode'] && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']); if ($isRemoved && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) { $tvP[1] = rawurlencode(@sprintf($noMatchingLabel, $evalValue)); } else { if (isset($parameterArray['fieldTSConfig']['altLabels.'][$evalValue])) { $tvP[1] = rawurlencode($languageService->sL($parameterArray['fieldTSConfig']['altLabels.'][$evalValue])); } if (isset($parameterArray['fieldTSConfig']['altIcons.'][$evalValue])) { $tvP[2] = $parameterArray['fieldTSConfig']['altIcons.'][$evalValue]; } } if ($tvP[1] == '') { // Case: flexform, default values supplied, no label provided (bug #9795) foreach ($selItems as $selItem) { if ($selItem[1] == $tvP[0]) { $tvP[1] = html_entity_decode($selItem[0]); break; } } } $itemArray[$tk] = implode('|', $tvP); } // size must be at least two, as there are always maxitems > 1 (see parent function) if (isset($config['size'])) { $size = (int) $config['size']; } else { $size = 2; } $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size; $itemsToSelect = ''; $filterTextfield = ''; $filterSelectbox = ''; if (!$disabled) { // Create option tags: $opt = array(); $styleAttrValue = ''; foreach ($selItems as $p) { if ($config['iconsInOptionTags']) { $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]); } $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . ' title="' . $p[0] . '">' . $p[0] . '</option>'; } // Put together the selector box: $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ''; $sOnChange = implode('', $parameterArray['fieldChangeFunc']); $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE)); $itemsToSelect = ' <select data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '" data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '_sel" ' . ' class="form-control t3js-formengine-select-itemstoselect" ' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $parameterArray['onFocus'] . $this->getValidationDataAsDataAttribute($config) . $selector_itemListStyle . '> ' . implode(' ', $opt) . ' </select>'; // enable filter functionality via a text field if ($config['enableMultiSelectFilterTextfield']) { $filterTextfield = ' <span class="input-group input-group-sm"> <span class="input-group-addon"> <span class="fa fa-filter"></span> </span> <input class="t3js-formengine-multiselect-filter-textfield form-control" value="" /> </span>'; } // enable filter functionality via a select if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) { $filterDropDownOptions = array(); foreach ($config['multiSelectFilterItems'] as $optionElement) { $optionValue = $languageService->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1] : $optionElement[0]); $filterDropDownOptions[] = '<option value="' . htmlspecialchars($languageService->sL($optionElement[0])) . '">' . htmlspecialchars($optionValue) . '</option>'; } $filterSelectbox = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown"> ' . implode(' ', $filterDropDownOptions) . ' </select>'; } } if (!empty(trim($filterSelectbox)) && !empty(trim($filterTextfield))) { $filterSelectbox = '<div class="form-multigroup-item form-multigroup-element">' . $filterSelectbox . '</div>'; $filterTextfield = '<div class="form-multigroup-item form-multigroup-element">' . $filterTextfield . '</div>'; $selectBoxFilterContents = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">' . $filterSelectbox . $filterTextfield . '</div>'; } else { $selectBoxFilterContents = trim($filterSelectbox . ' ' . $filterTextfield); } // Pass to "dbFileIcons" function: $params = array('size' => $size, 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0), 'style' => isset($config['selectedListStyle']) ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"' : '', 'dontShowMoveIcons' => $maxitems <= 1, 'maxitems' => $maxitems, 'info' => '', 'headers' => array('selector' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected'), 'items' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.items'), 'selectorbox' => $selectBoxFilterContents), 'noBrowser' => 1, 'rightbox' => $itemsToSelect, 'readOnly' => $disabled); $item .= $this->dbFileIcons($parameterArray['itemFormElName'], '', '', $itemArray, '', $params, $parameterArray['onFocus']); return $item; }