23560778b1bbffcd2231c7ec675eda92327b3c8d
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / FormInlineAjaxController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller;
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\Exception\AccessDeniedException;
18 use TYPO3\CMS\Backend\Form\FormDataCompiler;
19 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
20 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
21 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
22 use TYPO3\CMS\Backend\Form\NodeFactory;
23 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
24 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
25 use TYPO3\CMS\Core\DataHandling\DataHandler;
26 use TYPO3\CMS\Core\Http\AjaxRequestHandler;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Utility\MathUtility;
29
30 /**
31 * Handle FormEngine inline ajax calls
32 */
33 class FormInlineAjaxController {
34
35 /**
36 * @var InlineStackProcessor
37 */
38 protected $inlineStackProcessor;
39
40 /**
41 * General processor for AJAX requests concerning IRRE.
42 *
43 * @param array $_ Additional parameters (not used here)
44 * @param AjaxRequestHandler $ajaxObj The AjaxRequestHandler object of this request
45 * @throws \RuntimeException
46 * @return void
47 */
48 public function processInlineAjaxRequest($_, AjaxRequestHandler $ajaxObj) {
49 $this->inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
50 $ajaxArguments = GeneralUtility::_GP('ajax');
51 $ajaxIdParts = explode('::', $GLOBALS['ajaxID'], 2);
52 if (isset($ajaxArguments) && is_array($ajaxArguments) && !empty($ajaxArguments)) {
53 $ajaxMethod = $ajaxIdParts[1];
54 $ajaxObj->setContentFormat('jsonbody');
55 // @todo: ajaxArguments[2] is "returnUrl" in the first 3 calls - still needed?
56 switch ($ajaxMethod) {
57 case 'synchronizeLocalizeRecords':
58 $domObjectId = $ajaxArguments[0];
59 $type = $ajaxArguments[1];
60 // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
61 $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
62 $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
63 $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
64 $ajaxObj->setContent($this->renderInlineSynchronizeLocalizeRecords($type, $inlineFirstPid));
65 break;
66 case 'createNewRecord':
67 $domObjectId = $ajaxArguments[0];
68 $createAfterUid = 0;
69 if (isset($ajaxArguments[1])) {
70 $createAfterUid = $ajaxArguments[1];
71 }
72 // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
73 $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
74 $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
75 $ajaxObj->setContent($this->renderInlineNewChildRecord($domObjectId, $createAfterUid));
76 break;
77 case 'getRecordDetails':
78 $domObjectId = $ajaxArguments[0];
79 // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
80 $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
81 $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
82 $ajaxObj->setContent($this->renderInlineChildRecord($domObjectId));
83 break;
84 case 'setExpandedCollapsedState':
85 $domObjectId = $ajaxArguments[0];
86 // Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
87 $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId, FALSE);
88 $expand = $ajaxArguments[1];
89 $collapse = $ajaxArguments[2];
90 $this->setInlineExpandedCollapsedState($expand, $collapse);
91 break;
92 default:
93 throw new \RuntimeException('Not a valid ajax identifier', 1428227862);
94 }
95 }
96 }
97
98 /**
99 * Handle AJAX calls to dynamically load the form fields of a given inline record.
100 *
101 * @param string $domObjectId The calling object in hierarchy, that requested a new record.
102 * @return array An array to be used for JSON
103 */
104 protected function renderInlineChildRecord($domObjectId) {
105 // The current table - for this table we should add/import records
106 $current = $this->inlineStackProcessor->getUnstableStructure();
107 // The parent table - this table embeds the current table
108 $parent = $this->inlineStackProcessor->getStructureLevel(-1);
109 $config = $parent['config'];
110
111 if (empty($config['foreign_table']) || !is_array($GLOBALS['TCA'][$config['foreign_table']])) {
112 return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
113 }
114
115 $config = FormEngineUtility::mergeInlineConfiguration($config);
116
117 // Set flag in config so that only the fields are rendered
118 $config['renderFieldsOnly'] = TRUE;
119 $collapseAll = isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll'];
120 $expandSingle = isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle'];
121
122 $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
123 $record = $inlineRelatedRecordResolver->getRecord($current['table'], $current['uid']);
124
125 $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
126 // The HTML-object-id's prefix of the dynamically created record
127 $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $current['table'];
128 $objectId = $objectPrefix . '-' . $record['uid'];
129
130 $formDataInput = [];
131 $formDataInput['vanillaUid'] = (int)$parent['uid'];
132 $formDataInput['command'] = 'edit';
133 $formDataInput['tableName'] = $parent['table'];
134 $formDataInput['inlineFirstPid'] = $inlineFirstPid;
135 $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
136
137 /** @var TcaDatabaseRecord $formDataGroup */
138 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
139 /** @var FormDataCompiler $formDataCompiler */
140 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
141
142 $formData = $formDataCompiler->compile($formDataInput);
143 $formData['renderType'] = 'inlineRecordContainer';
144 $formData['inlineRelatedRecordToRender'] = $record;
145 $formData['inlineRelatedRecordConfig'] = $config;
146
147 try {
148 // Access to this record may be denied, create an according error message in this case
149 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
150 $childArray = $nodeFactory->create($formData)->render();
151 } catch (AccessDeniedException $e) {
152 return $this->getErrorMessageForAJAX('Access denied');
153 }
154
155 $jsonArray = [
156 'data' => '',
157 'stylesheetFiles' => [],
158 'scriptCall' => [],
159 ];
160 $jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
161 if ($config['foreign_unique']) {
162 $jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
163 }
164 if (!empty($childArray['inlineData'])) {
165 $jsonArray['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childArray['inlineData']) . ');';
166 }
167 $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
168 if ($config['appearance']['useSortable']) {
169 $inlineObjectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
170 $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
171 }
172 if (!$collapseAll && $expandSingle) {
173 $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
174 }
175
176 return $jsonArray;
177 }
178
179 /**
180 * Handle AJAX calls to show a new inline-record of the given table.
181 *
182 * @param string $domObjectId The calling object in hierarchy, that requested a new record.
183 * @param string|int $foreignUid If set, the new record should be inserted after that one.
184 * @return array An array to be used for JSON
185 */
186 protected function renderInlineNewChildRecord($domObjectId, $foreignUid) {
187 // The current table - for this table we should add/import records
188 $current = $this->inlineStackProcessor->getUnstableStructure();
189 // The parent table - this table embeds the current table
190 $parent = $this->inlineStackProcessor->getStructureLevel(-1);
191 $config = $parent['config'];
192
193 if (empty($config['foreign_table']) || !is_array($GLOBALS['TCA'][$config['foreign_table']])) {
194 return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
195 }
196
197 /** @var InlineRelatedRecordResolver $inlineRelatedRecordResolver */
198 $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
199
200 $config = FormEngineUtility::mergeInlineConfiguration($config);
201
202 $collapseAll = isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll'];
203 $expandSingle = isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle'];
204
205 $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
206
207 // Dynamically create a new record
208 if (!$foreignUid || !MathUtility::canBeInterpretedAsInteger($foreignUid) || $config['foreign_selector']) {
209 $record = $inlineRelatedRecordResolver->getNewRecord($inlineFirstPid, $current['table']);
210 // Set default values for new created records
211 if (isset($config['foreign_record_defaults']) && is_array($config['foreign_record_defaults'])) {
212 $foreignTableConfig = $GLOBALS['TCA'][$current['table']];
213 // The following system relevant fields can't be set by foreign_record_defaults
214 $notSettableFields = array(
215 'uid', 'pid', 't3ver_oid', 't3ver_id', 't3ver_label', 't3ver_wsid', 't3ver_state', 't3ver_stage',
216 't3ver_count', 't3ver_tstamp', 't3ver_move_id'
217 );
218 $configurationKeysForNotSettableFields = array(
219 'crdate', 'cruser_id', 'delete', 'origUid', 'transOrigDiffSourceField', 'transOrigPointerField',
220 'tstamp'
221 );
222 foreach ($configurationKeysForNotSettableFields as $configurationKey) {
223 if (isset($foreignTableConfig['ctrl'][$configurationKey])) {
224 $notSettableFields[] = $foreignTableConfig['ctrl'][$configurationKey];
225 }
226 }
227 foreach ($config['foreign_record_defaults'] as $fieldName => $defaultValue) {
228 if (isset($foreignTableConfig['columns'][$fieldName]) && !in_array($fieldName, $notSettableFields)) {
229 $record[$fieldName] = $defaultValue;
230 }
231 }
232 }
233 // Set language of new child record to the language of the parent record:
234 if ($parent['localizationMode'] === 'select' && MathUtility::canBeInterpretedAsInteger($parent['uid'])) {
235 $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']);
236 $parentLanguageField = $GLOBALS['TCA'][$parent['table']]['ctrl']['languageField'];
237 $childLanguageField = $GLOBALS['TCA'][$current['table']]['ctrl']['languageField'];
238 if ($parentRecord[$parentLanguageField] > 0) {
239 $record[$childLanguageField] = $parentRecord[$parentLanguageField];
240 }
241 }
242 } else {
243 // @todo: Check this: Else also hits if $foreignUid = 0?
244 $record = $inlineRelatedRecordResolver->getRecord($current['table'], $foreignUid);
245 }
246 // Now there is a foreign_selector, so there is a new record on the intermediate table, but
247 // this intermediate table holds a field, which is responsible for the foreign_selector, so
248 // we have to set this field to the uid we get - or if none, to a new uid
249 if ($config['foreign_selector'] && $foreignUid) {
250 $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_selector']);
251 // For a selector of type group/db, prepend the tablename (<tablename>_<uid>):
252 $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'] . '_';
253 $record[$config['foreign_selector']] .= $foreignUid;
254 if ($selConfig['table'] === 'sys_file') {
255 $fileRecord = $inlineRelatedRecordResolver->getRecord($selConfig['table'], $foreignUid);
256 if ($fileRecord !== FALSE && !$this->checkInlineFileTypeAccessForField($selConfig, $fileRecord)) {
257 return $this->getErrorMessageForAJAX('File extension ' . $fileRecord['extension'] . ' is not allowed here!');
258 }
259 }
260 }
261 // The HTML-object-id's prefix of the dynamically created record
262 $objectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
263 $objectPrefix = $objectName . '-' . $current['table'];
264 $objectId = $objectPrefix . '-' . $record['uid'];
265
266 $formDataInput = [];
267 $formDataInput['vanillaUid'] = (int)$parent['uid'];
268 $formDataInput['command'] = 'edit';
269 $formDataInput['tableName'] = $parent['table'];
270 $formDataInput['inlineFirstPid'] = $inlineFirstPid;
271 $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
272
273 if (!MathUtility::canBeInterpretedAsInteger($parent['uid']) && (int)$formDataInput['inlineFirstPid'] > 0) {
274 $formDataInput['command'] = 'new';
275 $formDataInput['vanillaUid'] = (int)$formDataInput['inlineFirstPid'];
276 }
277
278 /** @var TcaDatabaseRecord $formDataGroup */
279 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
280 /** @var FormDataCompiler $formDataCompiler */
281 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
282
283 $formData = $formDataCompiler->compile($formDataInput);
284 $formData['renderType'] = 'inlineRecordContainer';
285 $formData['inlineRelatedRecordToRender'] = $record;
286 $formData['inlineRelatedRecordConfig'] = $config;
287
288 try {
289 // Access to this record may be denied, create an according error message in this case
290 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
291 $childArray = $nodeFactory->create($formData)->render();
292 } catch (AccessDeniedException $e) {
293 return $this->getErrorMessageForAJAX('Access denied');
294 }
295
296 $jsonArray = [
297 'data' => '',
298 'stylesheetFiles' => [],
299 'scriptCall' => [],
300 ];
301 if (!$current['uid']) {
302 $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
303 $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',null,' . GeneralUtility::quoteJSvalue($foreignUid) . ');';
304 } else {
305 $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
306 $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',' . GeneralUtility::quoteJSvalue($current['uid']) . ',' . GeneralUtility::quoteJSvalue($foreignUid) . ');';
307 }
308 $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
309 if ($config['appearance']['useSortable']) {
310 $inlineObjectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
311 $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
312 }
313 if (!$collapseAll && $expandSingle) {
314 $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
315 }
316 // Fade out and fade in the new record in the browser view to catch the user's eye
317 $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
318
319 return $jsonArray;
320 }
321
322 /**
323 * Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent.
324 *
325 * @param string $type Defines the type 'localize' or 'synchronize' (string) or a single uid to be localized (int)
326 * @param int $inlineFirstPid Inline first pid
327 * @return array An array to be used for JSON
328 */
329 protected function renderInlineSynchronizeLocalizeRecords($type, $inlineFirstPid) {
330 $jsonArray = FALSE;
331 if (GeneralUtility::inList('localize,synchronize', $type) || MathUtility::canBeInterpretedAsInteger($type)) {
332 $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
333 // The parent level:
334 $parent = $this->inlineStackProcessor->getStructureLevel(-1);
335 $current = $this->inlineStackProcessor->getUnstableStructure();
336 $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']);
337
338 $cmd = array();
339 $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type;
340 /** @var $tce DataHandler */
341 $tce = GeneralUtility::makeInstance(DataHandler::class);
342 $tce->stripslashes_values = FALSE;
343 $tce->start(array(), $cmd);
344 $tce->process_cmdmap();
345
346 $oldItemList = $parentRecord[$parent['field']];
347 $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parent['field']];
348
349 $jsonArray = array(
350 'data' => '',
351 'stylesheetFiles' => [],
352 'scriptCall' => [],
353 );
354 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
355 $nameObjectForeignTable = $nameObject . '-' . $current['table'];
356 // Get the name of the field pointing to the original record:
357 $transOrigPointerField = $GLOBALS['TCA'][$current['table']]['ctrl']['transOrigPointerField'];
358 // Get the name of the field used as foreign selector (if any):
359 $foreignSelector = isset($parent['config']['foreign_selector']) && $parent['config']['foreign_selector'] ? $parent['config']['foreign_selector'] : FALSE;
360 // Convert lists to array with uids of child records:
361 $oldItems = FormEngineUtility::getInlineRelatedRecordsUidArray($oldItemList);
362 $newItems = FormEngineUtility::getInlineRelatedRecordsUidArray($newItemList);
363 // Determine the items that were localized or localized:
364 $removedItems = array_diff($oldItems, $newItems);
365 $localizedItems = array_diff($newItems, $oldItems);
366 // Set the items that should be removed in the forms view:
367 foreach ($removedItems as $item) {
368 $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $item) . ', {forceDirectRemoval: true});';
369 }
370 foreach ($localizedItems as $item) {
371 $row = $inlineRelatedRecordResolver->getRecord($current['table'], $item);
372 $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($row[$foreignSelector]) : 'null';
373
374 $formDataInput = [];
375 $formDataInput['vanillaUid'] = (int)$parent['uid'];
376 $formDataInput['command'] = 'edit';
377 $formDataInput['tableName'] = $parent['table'];
378 $formDataInput['inlineFirstPid'] = $inlineFirstPid;
379 $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
380
381 /** @var TcaDatabaseRecord $formDataGroup */
382 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
383 /** @var FormDataCompiler $formDataCompiler */
384 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
385
386 $formData = $formDataCompiler->compile($formDataInput);
387 $formData['renderType'] = 'inlineRecordContainer';
388 $formData['inlineRelatedRecordToRender'] = $row;
389 $formData['inlineRelatedRecordConfig'] = $parent['config'];
390
391 try {
392 // Access to this record may be denied, create an according error message in this case
393 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
394 $childArray = $nodeFactory->create($formData)->render();
395 } catch (AccessDeniedException $e) {
396 return $this->getErrorMessageForAJAX('Access denied');
397 }
398
399 $jsonArray['html'] .= $childArray['html'];
400 $jsonArray = [
401 'data' => '',
402 'stylesheetFiles' => [],
403 'scriptCall' => [],
404 ];
405 $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
406 $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($item) . ', null, ' . $selectedValue . ');';
407 // Remove possible virtual records in the form which showed that a child records could be localized:
408 if (isset($row[$transOrigPointerField]) && $row[$transOrigPointerField]) {
409 $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $row[$transOrigPointerField] . '_div') . ');';
410 }
411 }
412 if (!empty($jsonArray['data'])) {
413 array_unshift($jsonArray['scriptCall'], 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records') . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', json.data);');
414 }
415 }
416 return $jsonArray;
417 }
418
419 /**
420 * Merge stuff from child array into json array.
421 * This method is needed since ajax handling methods currently need to put scriptCalls before and after child code.
422 *
423 * @param array $jsonResult Given json result
424 * @param array $childResult Given child result
425 * @return array Merged json array
426 */
427 protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult) {
428 $jsonResult['data'] = $childResult['html'];
429 $jsonResult['stylesheetFiles'] = $childResult['stylesheetFiles'];
430 if (!empty($childResult['inlineData'])) {
431 $jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
432 }
433 if (!empty($childResult['additionalJavaScriptSubmit'])) {
434 $additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
435 $additionalJavaScriptSubmit = str_replace(array(CR, LF), '', $additionalJavaScriptSubmit);
436 $jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
437 }
438 foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
439 $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
440 }
441 $jsonResult['scriptCall'][] = $childResult['extJSCODE'];
442 foreach ($childResult['requireJsModules'] as $moduleName => $callbacks) {
443 if (!is_array($callbacks)) {
444 $callbacks = array($callbacks);
445 }
446 foreach ($callbacks as $callback) {
447 $inlineCodeKey = $moduleName;
448 $javaScriptCode = 'require(["' . $moduleName . '"]';
449 if ($callback !== NULL) {
450 $inlineCodeKey .= sha1($callback);
451 $javaScriptCode .= ', ' . $callback;
452 }
453 $javaScriptCode .= ');';
454 $jsonResult['scriptCall'][] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
455 }
456 }
457 return $jsonResult;
458 }
459
460 /**
461 * Save the expanded/collapsed state of a child record in the BE_USER->uc.
462 *
463 * @param string $expand Whether this record is expanded.
464 * @param string $collapse Whether this record is collapsed.
465 * @return void
466 */
467 protected function setInlineExpandedCollapsedState($expand, $collapse) {
468 $backendUser = $this->getBackendUserAuthentication();
469 // The current table - for this table we should add/import records
470 $currentTable = $this->inlineStackProcessor->getUnstableStructure();
471 $currentTable = $currentTable['table'];
472 // The top parent table - this table embeds the current table
473 $top = $this->inlineStackProcessor->getStructureLevel(0);
474 $topTable = $top['table'];
475 $topUid = $top['uid'];
476 $inlineView = $this->getInlineExpandCollapseStateArray();
477 // Only do some action if the top record and the current record were saved before
478 if (MathUtility::canBeInterpretedAsInteger($topUid)) {
479 $expandUids = GeneralUtility::trimExplode(',', $expand);
480 $collapseUids = GeneralUtility::trimExplode(',', $collapse);
481 // Set records to be expanded
482 foreach ($expandUids as $uid) {
483 $inlineView[$topTable][$topUid][$currentTable][] = $uid;
484 }
485 // Set records to be collapsed
486 foreach ($collapseUids as $uid) {
487 $inlineView[$topTable][$topUid][$currentTable] = $this->removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
488 }
489 // Save states back to database
490 if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
491 $inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
492 $backendUser->uc['inlineView'] = serialize($inlineView);
493 $backendUser->writeUC();
494 }
495 }
496 }
497
498 /**
499 * Checks if a record selector may select a certain file type
500 *
501 * @param array $selectorConfiguration
502 * @param array $fileRecord
503 * @return bool
504 */
505 protected function checkInlineFileTypeAccessForField(array $selectorConfiguration, array $fileRecord) {
506 if (!empty($selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'])) {
507 $allowedFileExtensions = GeneralUtility::trimExplode(
508 ',',
509 $selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'],
510 TRUE
511 );
512 if (!in_array(strtolower($fileRecord['extension']), $allowedFileExtensions, TRUE)) {
513 return FALSE;
514 }
515 }
516 return TRUE;
517 }
518
519 /**
520 * Return expand / collapse state array for a given table / uid combination
521 *
522 * @param string $table Handled table
523 * @param int $uid Handled uid
524 * @return array
525 */
526 protected function getInlineExpandCollapseStateArrayForTableUid($table, $uid) {
527 $inlineView = $this->getInlineExpandCollapseStateArray();
528 $result = array();
529 if (MathUtility::canBeInterpretedAsInteger($uid)) {
530 if (!empty($inlineView[$table][$uid])) {
531 $result = $inlineView[$table][$uid];
532 }
533 }
534 return $result;
535 }
536
537 /**
538 * Get expand / collapse state of inline items
539 *
540 * @return array
541 */
542 protected function getInlineExpandCollapseStateArray() {
543 $backendUser = $this->getBackendUserAuthentication();
544 $inlineView = unserialize($backendUser->uc['inlineView']);
545 if (!is_array($inlineView)) {
546 $inlineView = array();
547 }
548 return $inlineView;
549 }
550
551 /**
552 * Remove an element from an array.
553 *
554 * @param mixed $needle The element to be removed.
555 * @param array $haystack The array the element should be removed from.
556 * @param mixed $strict Search elements strictly.
557 * @return array The array $haystack without the $needle
558 */
559 protected function removeFromArray($needle, $haystack, $strict = NULL) {
560 $pos = array_search($needle, $haystack, $strict);
561 if ($pos !== FALSE) {
562 unset($haystack[$pos]);
563 }
564 return $haystack;
565 }
566
567 /**
568 * Generates an error message that transferred as JSON for AJAX calls
569 *
570 * @param string $message The error message to be shown
571 * @return array The error message in a JSON array
572 */
573 protected function getErrorMessageForAJAX($message) {
574 return [
575 'data' => $message,
576 'scriptCall' => [
577 'alert("' . $message . '");'
578 ],
579 ];
580 }
581
582 /**
583 * @return BackendUserAuthentication
584 */
585 protected function getBackendUserAuthentication() {
586 return $GLOBALS['BE_USER'];
587 }
588
589 }