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