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