191ad96db81645730343a7642f06caced4ee20ec
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormResultCompiler.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form;
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\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Page\PageRenderer;
19 use TYPO3\CMS\Core\Utility\ArrayUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * This is form engine - Class for creating the backend editing forms.
24 *
25 * @internal This class and its exposed method and method signatures will change
26 */
27 class FormResultCompiler
28 {
29 /**
30 * @var array HTML of additional hidden fields rendered by sub containers
31 */
32 protected $hiddenFieldAccum = [];
33
34 /**
35 * Can be set to point to a field name in the form which will be set to '1' when the form
36 * is submitted with a *save* button. This way the recipient script can determine that
37 * the form was submitted for save and not "close" for example.
38 *
39 * @var string
40 */
41 protected $doSaveFieldName = '';
42
43 /**
44 * @var array Data array from IRRE pushed to frontend as json array
45 */
46 protected $inlineData = [];
47
48 /**
49 * List of additional style sheet files to load
50 *
51 * @var array
52 */
53 protected $stylesheetFiles = [];
54
55 /**
56 * Additional JavaScript printed after the form
57 *
58 * @var array
59 */
60 protected $additionalJavaScriptPost = [];
61
62 /**
63 * Additional JavaScript executed on submit; If you set "OK" variable it will raise an error
64 * about RTEs not being loaded and offer to block further submission.
65 *
66 * @var array
67 */
68 protected $additionalJavaScriptSubmit = [];
69
70 /**
71 * Additional language label files to include.
72 *
73 * @var array
74 */
75 protected $additionalInlineLanguageLabelFiles = [];
76
77 /**
78 * Array with requireJS modules, use module name as key, the value could be callback code.
79 * Use NULL as value if no callback is used.
80 *
81 * @var array
82 */
83 protected $requireJsModules = [];
84
85 /**
86 * @var PageRenderer
87 */
88 protected $pageRenderer;
89
90 /**
91 * Merge existing data with the given result array
92 *
93 * @param array $resultArray Array returned by child
94 * @internal Temporary method to use FormEngine class as final data merger
95 */
96 public function mergeResult(array $resultArray)
97 {
98 $this->doSaveFieldName = $resultArray['doSaveFieldName'];
99 foreach ($resultArray['additionalJavaScriptPost'] as $element) {
100 $this->additionalJavaScriptPost[] = $element;
101 }
102 foreach ($resultArray['additionalJavaScriptSubmit'] as $element) {
103 $this->additionalJavaScriptSubmit[] = $element;
104 }
105 if (!empty($resultArray['requireJsModules'])) {
106 foreach ($resultArray['requireJsModules'] as $module) {
107 $moduleName = null;
108 $callback = null;
109 if (is_string($module)) {
110 // if $module is a string, no callback
111 $moduleName = $module;
112 $callback = null;
113 } elseif (is_array($module)) {
114 // if $module is an array, callback is possible
115 foreach ($module as $key => $value) {
116 $moduleName = $key;
117 $callback = $value;
118 break;
119 }
120 }
121 if ($moduleName !== null) {
122 if (!empty($this->requireJsModules[$moduleName]) && $callback !== null) {
123 $existingValue = $this->requireJsModules[$moduleName];
124 if (!is_array($existingValue)) {
125 $existingValue = [$existingValue];
126 }
127 $existingValue[] = $callback;
128 $this->requireJsModules[$moduleName] = $existingValue;
129 } else {
130 $this->requireJsModules[$moduleName] = $callback;
131 }
132 }
133 }
134 }
135
136 foreach ($resultArray['additionalHiddenFields'] as $element) {
137 $this->hiddenFieldAccum[] = $element;
138 }
139 foreach ($resultArray['stylesheetFiles'] as $stylesheetFile) {
140 if (!in_array($stylesheetFile, $this->stylesheetFiles)) {
141 $this->stylesheetFiles[] = $stylesheetFile;
142 }
143 }
144
145 if (!empty($resultArray['inlineData'])) {
146 $resultArrayInlineData = $this->inlineData;
147 $resultInlineData = $resultArray['inlineData'];
148 ArrayUtility::mergeRecursiveWithOverrule($resultArrayInlineData, $resultInlineData);
149 $this->inlineData = $resultArrayInlineData;
150 }
151
152 if (!empty($resultArray['additionalInlineLanguageLabelFiles'])) {
153 foreach ($resultArray['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
154 $this->additionalInlineLanguageLabelFiles[] = $additionalInlineLanguageLabelFile;
155 }
156 }
157 }
158
159 /**
160 * Adds CSS files BEFORE the form is drawn
161 *
162 * @return string
163 */
164 public function addCssFiles()
165 {
166 $pageRenderer = $this->getPageRenderer();
167 foreach ($this->stylesheetFiles as $stylesheetFile) {
168 $pageRenderer->addCssFile($stylesheetFile);
169 }
170 return '';
171 }
172
173 /**
174 * Prints necessary JavaScript for TCEforms (after the form HTML).
175 * currently this is used to transform page-specific options in the TYPO3.Settings array for JS
176 * so the JS module can access these values
177 *
178 * @return string
179 */
180 public function printNeededJSFunctions()
181 {
182 // set variables to be accessible for JS
183 $pageRenderer = $this->getPageRenderer();
184 $pageRenderer->addInlineSetting('FormEngine', 'formName', 'editform');
185
186 return $this->JSbottom();
187 }
188
189 /**
190 * JavaScript bottom code
191 *
192 * @return string A section with JavaScript - if $update is FALSE, embedded in <script></script>
193 */
194 protected function JSbottom()
195 {
196 $pageRenderer = $this->getPageRenderer();
197 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
198 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
199
200 // @todo: this is messy here - "additional hidden fields" should be handled elsewhere
201 $html = implode(LF, $this->hiddenFieldAccum);
202 $pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/md5.js');
203 // load the main module for FormEngine with all important JS functions
204 $this->requireJsModules['TYPO3/CMS/Backend/FormEngine'] = 'function(FormEngine) {
205 FormEngine.initialize(
206 ' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('wizard_element_browser')) . ',
207 ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . '
208 );
209 }';
210 $this->requireJsModules['TYPO3/CMS/Backend/FormEngineReview'] = null;
211
212 foreach ($this->requireJsModules as $moduleName => $callbacks) {
213 if (!is_array($callbacks)) {
214 $callbacks = [$callbacks];
215 }
216 foreach ($callbacks as $callback) {
217 $pageRenderer->loadRequireJsModule($moduleName, $callback);
218 }
219 }
220 $pageRenderer->loadJquery();
221 $backendUser = $this->getBackendUserAuthentication();
222
223 // define the window size of the element browser etc.
224 $popupWindowWidth = 800;
225 $popupWindowHeight = 600;
226 $popupWindowSize = trim($backendUser->getTSConfig()['options.']['popupWindowSize'] ?? '');
227 if (!empty($popupWindowSize)) {
228 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
229 }
230
231 // define the window size of the popups within the RTE
232 $rtePopupWindowSize = trim($backendUser->getTSConfig()['options.']['rte.']['popupWindowSize'] ?? '');
233 if (!empty($rtePopupWindowSize)) {
234 list($rtePopupWindowWidth, $rtePopupWindowHeight) = GeneralUtility::trimExplode('x', $rtePopupWindowSize);
235 }
236 $rtePopupWindowWidth = !empty($rtePopupWindowWidth) ? (int)$rtePopupWindowWidth : $popupWindowWidth;
237 $rtePopupWindowHeight = !empty($rtePopupWindowHeight) ? (int)$rtePopupWindowHeight : $popupWindowHeight;
238
239 // Make textareas resizable and flexible ("autogrow" in height)
240 $textareaSettings = [
241 'autosize' => (bool)$backendUser->uc['resizeTextareas_Flexible'],
242 'RTEPopupWindow' => [
243 'width' => $rtePopupWindowWidth,
244 'height' => $rtePopupWindowHeight
245 ]
246 ];
247 $pageRenderer->addInlineSettingArray('Textarea', $textareaSettings);
248
249 $popupSettings = [
250 'PopupWindow' => [
251 'width' => $popupWindowWidth,
252 'height' => $popupWindowHeight
253 ]
254 ];
255 $pageRenderer->addInlineSettingArray('Popup', $popupSettings);
256
257 $pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js');
258 // Needed for FormEngine manipulation (date picker)
259 $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
260 $pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
261
262 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileListLocalisation');
263
264 $pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf', 'file_upload');
265 if (!empty($this->additionalInlineLanguageLabelFiles)) {
266 foreach ($this->additionalInlineLanguageLabelFiles as $additionalInlineLanguageLabelFile) {
267 $pageRenderer->addInlineLanguageLabelFile($additionalInlineLanguageLabelFile);
268 }
269 }
270 // We want to load jQuery-ui inside our js. Enable this using requirejs.
271 $pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/jsfunc.inline.js');
272
273 // todo: change these things in JS
274 $pageRenderer->addInlineLanguageLabelArray([
275 'FormEngine.noRecordTitle' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title'),
276 'FormEngine.fieldsChanged' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.fieldsChanged'),
277 'FormEngine.fieldsMissing' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.fieldsMissing'),
278 'FormEngine.maxItemsAllowed' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.maxItemsAllowed'),
279 'FormEngine.refreshRequiredTitle' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.refreshRequired.title'),
280 'FormEngine.refreshRequiredContent' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.refreshRequired.content'),
281 'FormEngine.remainingCharacters' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remainingCharacters'),
282 ]);
283
284 $out = LF . 'TBE_EDITOR.doSaveFieldName = "' . ($this->doSaveFieldName ? addslashes($this->doSaveFieldName) : '') . '";';
285
286 // Add JS required for inline fields
287 if (!empty($this->inlineData)) {
288 $out .= LF . 'inline.addToDataArray(' . json_encode($this->inlineData) . ');';
289 }
290 // $this->additionalJS_submit:
291 if ($this->additionalJavaScriptSubmit) {
292 $additionalJS_submit = implode('', $this->additionalJavaScriptSubmit);
293 $additionalJS_submit = str_replace([CR, LF], '', $additionalJS_submit);
294 $out .= 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJS_submit) . '");';
295 }
296 $out .= LF . implode(LF, $this->additionalJavaScriptPost);
297
298 return $html . LF . "\t" . GeneralUtility::wrapJS($out);
299 }
300
301 /**
302 * @return BackendUserAuthentication
303 */
304 protected function getBackendUserAuthentication()
305 {
306 return $GLOBALS['BE_USER'];
307 }
308
309 /**
310 * Returns an instance of LanguageService
311 *
312 * @return \TYPO3\CMS\Core\Localization\LanguageService
313 */
314 protected function getLanguageService()
315 {
316 return $GLOBALS['LANG'];
317 }
318
319 /**
320 * Wrapper for access to the current page renderer object
321 *
322 * @return \TYPO3\CMS\Core\Page\PageRenderer
323 */
324 protected function getPageRenderer()
325 {
326 if ($this->pageRenderer === null) {
327 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
328 }
329 return $this->pageRenderer;
330 }
331 }