[BUGFIX] Handle langChildren correctly in flex form
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Container / FlexFormElementContainer.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Container;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Form\ElementConditionMatcher;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Lang\LanguageService;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
22 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
24
25 /**
26 * The container handles single elements.
27 *
28 * This one is called by FlexFormTabsContainer, FlexFormNoTabsContainer or FlexFormContainerContainer.
29 * For single fields, the code is similar to SingleFieldContainer, processing will end up in single
30 * element classes depending on specific type of an element. Additionally, it determines if a
31 * section is handled and hands over to FlexFormSectionContainer in this case.
32 */
33 class FlexFormElementContainer extends AbstractContainer {
34
35 /**
36 * Entry method
37 *
38 * @return array As defined in initializeResultArray() of AbstractNode
39 */
40 public function render() {
41 $table = $this->data['tableName'];
42 $row = $this->data['databaseRow'];
43 $fieldName = $this->data['fieldName'];
44 $flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
45 $flexFormRowData = $this->data['flexFormRowData'];
46 $flexFormFormPrefix = $this->data['flexFormFormPrefix'];
47 $parameterArray = $this->data['parameterArray'];
48 $metaData = $this->data['parameterArray']['fieldConf']['config']['ds']['meta'];
49
50 $languageService = $this->getLanguageService();
51 $resultArray = $this->initializeResultArray();
52 foreach ($flexFormDataStructureArray as $flexFormFieldName => $flexFormFieldArray) {
53 if (
54 // No item array found at all
55 !is_array($flexFormFieldArray)
56 // Not a section or container and not a list of single items
57 || (!isset($flexFormFieldArray['type']) && !is_array($flexFormFieldArray['config']))
58 ) {
59 continue;
60 }
61
62 if ($flexFormFieldArray['type'] === 'array') {
63 // Section
64 if (empty($flexFormFieldArray['section'])) {
65 $resultArray['html'] = LF . 'Section expected at ' . $flexFormFieldName . ' but not found';
66 continue;
67 }
68
69 $sectionTitle = '';
70 if (!empty($flexFormFieldArray['title'])) {
71 $sectionTitle = $languageService->sL($flexFormFieldArray['title']);
72 }
73
74 $options = $this->data;
75 $options['flexFormDataStructureArray'] = $flexFormFieldArray['el'];
76 $options['flexFormRowData'] = is_array($flexFormRowData[$flexFormFieldName]['el']) ? $flexFormRowData[$flexFormFieldName]['el'] : array();
77 $options['flexFormSectionType'] = $flexFormFieldName;
78 $options['flexFormSectionTitle'] = $sectionTitle;
79 $options['renderType'] = 'flexFormSectionContainer';
80 $sectionContainerResult = $this->nodeFactory->create($options)->render();
81 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $sectionContainerResult);
82 } else {
83 // Single element
84 $vDEFkey = 'vDEF';
85
86 if (is_array($metaData) && isset($metaData['langChildren']) && isset($metaData['languagesOnElement'])) {
87 $lkeys = $metaData['languagesOnElement'];
88 array_walk($lkeys, function (&$value) {
89 $value = 'v' . $value;
90 });
91 } else {
92 $lkeys = array($vDEFkey);
93 }
94 $html = array();
95 $html[] = '<div class="form-section">';
96 foreach ($lkeys as $lkey) {
97 $displayConditionResult = TRUE;
98 if (!empty($flexFormFieldArray['displayCond'])) {
99 $conditionData = is_array($flexFormRowData) ? $flexFormRowData : array();
100 $conditionData['parentRec'] = $row;
101 /** @var $elementConditionMatcher ElementConditionMatcher */
102 $elementConditionMatcher = GeneralUtility::makeInstance(ElementConditionMatcher::class);
103 $displayConditionResult = $elementConditionMatcher->match($flexFormFieldArray['displayCond'], $conditionData, $lkey);
104 }
105 if (!$displayConditionResult) {
106 continue;
107 }
108
109 // Set up options for single element
110 $fakeParameterArray = array(
111 'fieldConf' => array(
112 'label' => $languageService->sL(trim($flexFormFieldArray['label'])),
113 'config' => $flexFormFieldArray['config'],
114 'defaultExtras' => $flexFormFieldArray['defaultExtras'],
115 'onChange' => $flexFormFieldArray['onChange'],
116 ),
117 );
118
119 $alertMsgOnChange = '';
120 if (
121 $fakeParameterArray['fieldConf']['onChange'] === 'reload'
122 || !empty($GLOBALS['TCA'][$table]['ctrl']['type']) && $GLOBALS['TCA'][$table]['ctrl']['type'] === $flexFormFieldName
123 || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate']) && GeneralUtility::inList($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'], $flexFormFieldName)
124 ) {
125 if ($this->getBackendUserAuthentication()->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
126 $alertMsgOnChange = 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
127 } else {
128 $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm();}';
129 }
130 }
131 $fakeParameterArray['fieldChangeFunc'] = $parameterArray['fieldChangeFunc'];
132 if ($alertMsgOnChange) {
133 $fakeParameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
134 }
135
136 $fakeParameterArray['onFocus'] = $parameterArray['onFocus'];
137 $fakeParameterArray['label'] = $parameterArray['label'];
138 $fakeParameterArray['itemFormElName'] = $parameterArray['itemFormElName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $lkey . ']';
139 $fakeParameterArray['itemFormElID'] = $fakeParameterArray['itemFormElName'];
140 if (isset($flexFormRowData[$flexFormFieldName][$lkey])) {
141 $fakeParameterArray['itemFormElValue'] = $flexFormRowData[$flexFormFieldName][$lkey];
142 } else {
143 $fakeParameterArray['itemFormElValue'] = $fakeParameterArray['fieldConf']['config']['default'];
144 }
145
146 $options = $this->data;
147 $options['parameterArray'] = $fakeParameterArray;
148 $options['elementBaseName'] = $this->data['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $lkey . ']';
149
150 if (!empty($flexFormFieldArray['config']['renderType'])) {
151 $options['renderType'] = $flexFormFieldArray['config']['renderType'];
152 } else {
153 // Fallback to type if no renderType is given
154 $options['renderType'] = $flexFormFieldArray['config']['type'];
155 }
156 $childResult = $this->nodeFactory->create($options)->render();
157
158 $theTitle = htmlspecialchars($fakeParameterArray['fieldConf']['label']);
159 $defInfo = array();
160
161 $languageIcon = '';
162 if ($vDEFkey !== 'vDEF') {
163 $languageIcon = FormEngineUtility::getLanguageIcon($table, $row, $vDEFkey);
164 }
165 // Possible line breaks in the label through xml: \n => <br/>, usage of nl2br() not possible, so it's done through str_replace (?!)
166 $processedTitle = str_replace('\\n', '<br />', $theTitle);
167 // @todo: Similar to the processing within SingleElementContainer ... use it from there?!
168 $html[] = '<div class="form-group t3js-formengine-palette-field t3js-formengine-validation-marker">';
169 $html[] = '<label class="t3js-formengine-label">';
170 $html[] = $languageIcon;
171 if (is_array($metaData) && isset($metaData['langChildren'])) {
172 $html[] = FormEngineUtility::getLanguageIcon($table, $row, $lkey);
173 }
174 $html[] = BackendUtility::wrapInHelp($parameterArray['_cshKey'], $flexFormFieldName, $processedTitle);
175 $html[] = '</label>';
176 $html[] = '<div class="t3js-formengine-field-item">';
177 $html[] = $childResult['html'];
178 $html[] = implode(LF, $defInfo);
179 $html[] = $this->renderVDEFDiff($flexFormRowData[$flexFormFieldName], $lkey);
180 $html[] = '</div>';
181 $html[] = '</div>';
182 }
183 $html[] = '</div>';
184
185 $resultArray['html'] .= implode(LF, $html);
186 $childResult['html'] = '';
187 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult);
188 }
189 }
190
191 return $resultArray;
192 }
193
194 /**
195 * Renders the diff-view of vDEF fields in flex forms
196 *
197 * @param array $vArray Record array of the record being edited
198 * @param string $vDEFkey HTML of the form field. This is what we add the content to.
199 * @return string Item string returned again, possibly with the original value added to.
200 */
201 protected function renderVDEFDiff($vArray, $vDEFkey) {
202 $item = NULL;
203 if (
204 $GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase'] && isset($vArray[$vDEFkey . '.vDEFbase'])
205 && (string)$vArray[$vDEFkey . '.vDEFbase'] !== (string)$vArray['vDEF'][0]
206 ) {
207 // Create diff-result:
208 $diffUtility = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\DiffUtility::class);
209 $diffres = $diffUtility->makeDiffDisplay($vArray[$vDEFkey . '.vDEFbase'], $vArray['vDEF']);
210 $item = '<div class="typo3-TCEforms-diffBox">' . '<div class="typo3-TCEforms-diffBox-header">'
211 . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.changeInOrig')) . ':</div>' . $diffres . '</div>';
212 }
213 return $item;
214 }
215
216 /**
217 * @return LanguageService
218 */
219 protected function getLanguageService() {
220 return $GLOBALS['LANG'];
221 }
222
223 /**
224 * @return BackendUserAuthentication
225 */
226 protected function getBackendUserAuthentication() {
227 return $GLOBALS['BE_USER'];
228 }
229
230 }