[BUGFIX] FormEngine: FlexForm section container not saved
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Container / FlexFormSectionContainer.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\Utility\IconUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Lang\LanguageService;
21
22 /**
23 * Handle flex form sections.
24 *
25 * This container is created by FlexFormElementContainer if a "single" element is in
26 * fact a sections. For each existing section container it creates as FlexFormContainerContainer
27 * to render its inner fields, additionally for each possible container a "template" of this
28 * container type is rendered and added - to be added by JS to DOM on click on "new xy container".
29 */
30 class FlexFormSectionContainer extends AbstractContainer {
31
32 /**
33 * Entry method
34 *
35 * @return array As defined in initializeResultArray() of AbstractNode
36 */
37 public function render() {
38 $languageService = $this->getLanguageService();
39
40 $flexFormFieldsArray = $this->globalOptions['flexFormDataStructureArray'];
41 $flexFormRowData = $this->globalOptions['flexFormRowData'];
42 $flexFormFieldIdentifierPrefix = $this->globalOptions['flexFormFieldIdentifierPrefix'];
43 $flexFormSectionType = $this->globalOptions['flexFormSectionType'];
44 $flexFormSectionTitle = $this->globalOptions['flexFormSectionTitle'];
45
46 $userHasAccessToDefaultLanguage = $this->getBackendUserAuthentication()->checkLanguageAccess(0);
47
48 $resultArray = $this->initializeResultArray();
49
50 // Creating IDs for form fields:
51 // It's important that the IDs "cascade" - otherwise we can't dynamically expand the flex form
52 // because this relies on simple string substitution of the first parts of the id values.
53 $flexFormFieldIdentifierPrefix = $flexFormFieldIdentifierPrefix . '-' . GeneralUtility::shortMd5(uniqid('id', TRUE));
54
55 // Render each existing container
56 foreach ($flexFormRowData as $flexFormContainerCounter => $existingSectionContainerData) {
57 // @todo: This relies on the fact that "_TOGGLE" is *below* the real data in the saved xml structure
58 if (is_array($existingSectionContainerData)) {
59 $existingSectionContainerDataStructureType = key($existingSectionContainerData);
60 $existingSectionContainerData = $existingSectionContainerData[$existingSectionContainerDataStructureType];
61 $containerDataStructure = $flexFormFieldsArray[$existingSectionContainerDataStructureType];
62 // There may be cases where a field is still in DB but does not exist in definition
63 if (is_array($containerDataStructure)) {
64 $sectionTitle = '';
65 if (!empty($containerDataStructure['title'])) {
66 $sectionTitle = $languageService->sL($containerDataStructure['title']);
67 }
68
69 $options = $this->globalOptions;
70 $options['flexFormRowData'] = $existingSectionContainerData['el'];
71 $options['flexFormDataStructureArray'] = $containerDataStructure['el'];
72 $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
73 $options['flexFormFormPrefix'] = $this->globalOptions['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
74 $options['flexFormContainerName'] = $existingSectionContainerDataStructureType;
75 $options['flexFormContainerCounter'] = $flexFormContainerCounter;
76 $options['flexFormContainerTitle'] = $sectionTitle;
77 $options['flexFormContainerElementCollapsed'] = (bool)$existingSectionContainerData['el']['_TOGGLE'];
78 /** @var FlexFormContainerContainer $flexFormContainerContainer */
79 $flexFormContainerContainer = GeneralUtility::makeInstance(FlexFormContainerContainer::class);
80 $flexFormContainerContainerResult = $flexFormContainerContainer->setGlobalOptions($options)->render();
81 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerResult);
82 }
83 }
84 }
85
86 // "New container" handling: Creates a "template" of each possible container and stuffs it
87 // somewhere into DOM to be handled with JS magic.
88 // Fun part: Handle the fact that requiredElements and such may be set for children
89 $containerTemplatesHtml = array();
90 foreach ($flexFormFieldsArray as $flexFormContainerName => $flexFormFieldDefinition) {
91 $containerTemplateHtml = array();
92 $sectionTitle = '';
93 if (!empty($flexFormFieldDefinition['title'])) {
94 $sectionTitle = $languageService->sL($flexFormFieldDefinition['title']);
95 }
96
97 $options = $this->globalOptions;
98 $options['flexFormRowData'] = array();
99 $options['flexFormDataStructureArray'] = $flexFormFieldDefinition['el'];
100 $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
101 $options['flexFormFormPrefix'] = $this->globalOptions['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
102 $options['flexFormContainerName'] = $flexFormContainerName;
103 $options['flexFormContainerCounter'] = $flexFormFieldIdentifierPrefix . '-form';
104 $options['flexFormContainerTitle'] = $sectionTitle;
105 $options['flexFormContainerElementCollapsed'] = FALSE;
106 /** @var FlexFormContainerContainer $flexFormContainerContainer */
107 $flexFormContainerContainerTemplate = GeneralUtility::makeInstance(FlexFormContainerContainer::class);
108 $flexFormContainerContainerTemplateResult = $flexFormContainerContainerTemplate->setGlobalOptions($options)->render();
109
110 $uniqueId = str_replace('.', '', uniqid('idvar', TRUE));
111 $identifierPrefixJs = 'replace(/' . $flexFormFieldIdentifierPrefix . '-/g,"' . $flexFormFieldIdentifierPrefix . '-"+' . $uniqueId . '+"-")';
112 $identifierPrefixJs .= '.replace(/(tceforms-(datetime|date)field-)/g,"$1" + (new Date()).getTime())';
113
114 $onClickInsert = array();
115 $onClickInsert[] = 'var ' . $uniqueId . ' = "' . 'idx"+(new Date()).getTime();';
116 $onClickInsert[] = 'new Insertion.Bottom($("' . $flexFormFieldIdentifierPrefix . '"), ' . json_encode($flexFormContainerContainerTemplateResult['html']) . '.' . $identifierPrefixJs . ');';
117 $onClickInsert[] = 'TYPO3.jQuery("#' . $flexFormFieldIdentifierPrefix . '").t3FormEngineFlexFormElement();';
118 $onClickInsert[] = 'eval(unescape("' . rawurlencode(implode(';', $flexFormContainerContainerTemplateResult['additionalJavaScriptPost'])) . '").' . $identifierPrefixJs . ');';
119 $onClickInsert[] = 'TBE_EDITOR.addActionChecks("submit", unescape("' . rawurlencode(implode(';', $flexFormContainerContainerTemplateResult['additionalJavaScriptSubmit'])) . '").' . $identifierPrefixJs . ');';
120 $onClickInsert[] = 'TYPO3.FormEngine.reinitialize();';
121 $onClickInsert[] = 'return false;';
122
123 $containerTemplateHtml[] = '<a href="#" onclick="' . htmlspecialchars(implode(LF, $onClickInsert)) . '">';
124 $containerTemplateHtml[] = IconUtility::getSpriteIcon('actions-document-new');
125 $containerTemplateHtml[] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($sectionTitle, 30));
126 $containerTemplateHtml[] = '</a>';
127 $containerTemplatesHtml[] = implode(LF, $containerTemplateHtml);
128
129 $flexFormContainerContainerTemplateResult['html'] = '';
130 $flexFormContainerContainerTemplateResult['additionalJavaScriptPost'] = array();
131 $flexFormContainerContainerTemplateResult['additionalJavaScriptSubmit'] = array();
132
133 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerTemplateResult);
134 }
135
136 // Create new elements links
137 $createElementsHtml = array();
138 if ($userHasAccessToDefaultLanguage) {
139 $createElementsHtml[] = '<div class="t3-form-field-add-flexsection">';
140 $createElementsHtml[] = '<strong>';
141 $createElementsHtml[] = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.addnew', TRUE) . ':';
142 $createElementsHtml[] = '</strong>';
143 $createElementsHtml[] = implode('|', $containerTemplatesHtml);
144 $createElementsHtml[] = '</div>';
145 }
146
147 // Wrap child stuff
148 $toggleAll = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.toggleall', TRUE);
149 $html = array();
150 $html[] = '<div class="t3-form-field-container t3-form-flex">';
151 $html[] = '<div class="t3-form-field-label-flexsection">';
152 $html[] = '<strong>';
153 $html[] = htmlspecialchars($flexFormSectionTitle);
154 $html[] = '</strong>';
155 $html[] = '</div>';
156 $html[] = '<div class="t3-form-field-toggle-flexsection t3-form-flexsection-toggle">';
157 $html[] = '<a href="#">';
158 $html[] = IconUtility::getSpriteIcon('actions-move-right', array('title' => $toggleAll)) . $toggleAll;
159 $html[] = '</a>';
160 $html[] = '</div>';
161 $html[] = '<div';
162 $html[] = 'id="' . $flexFormFieldIdentifierPrefix . '"';
163 $html[] = 'class="t3-form-field-container-flexsection t3-flex-container"';
164 $html[] = 'data-t3-flex-allow-restructure="' . ($userHasAccessToDefaultLanguage ? '1' : '0') . '"';
165 $html[] = '>';
166 $html[] = $resultArray['html'];
167 $html[] = '</div>';
168 $html[] = implode(LF, $createElementsHtml);
169 $html[] = '</div>';
170
171 $resultArray['html'] = implode(LF, $html);
172
173 return $resultArray;
174 }
175
176 /**
177 * @return BackendUserAuthentication
178 */
179 protected function getBackendUserAuthentication() {
180 return $GLOBALS['BE_USER'];
181 }
182
183 /**
184 * @return LanguageService
185 */
186 protected function getLanguageService() {
187 return $GLOBALS['LANG'];
188 }
189
190 }