[CLEANUP] Replace count with empty in EXT:backend
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Utility / FormEngineUtility.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Utility;
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\Module\ModuleLoader;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Utility\PathUtility;
21 use TYPO3\CMS\Backend\Utility\IconUtility;
22 use TYPO3\CMS\Lang\LanguageService;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
24 use TYPO3\CMS\Core\Database\DatabaseConnection;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26 use TYPO3\CMS\Core\Messaging\FlashMessage;
27 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
28 use TYPO3\CMS\Core\Messaging\FlashMessageService;
29 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
30 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
31 use TYPO3\CMS\Backend\Form\DataPreprocessor;
32
33 /**
34 * This is a static, internal and intermediate helper class for various
35 * FormEngine related tasks.
36 *
37 * This class was introduced to help disentangling FormEngine and
38 * its sub classes. It MUST NOT be used in other extensions and will
39 * change or vanish without further notice.
40 *
41 * @internal
42 * @todo: These helpers are target to be dropped if further FormEngine refactoring is done
43 */
44 class FormEngineUtility {
45
46 /**
47 * Whitelist that allows TCA field configuration to be overridden by TSconfig
48 *
49 * @see overrideFieldConf()
50 * @var array
51 */
52 static protected $allowOverrideMatrix = array(
53 'input' => array('size', 'max', 'readOnly'),
54 'text' => array('cols', 'rows', 'wrap', 'readOnly'),
55 'check' => array('cols', 'showIfRTE', 'readOnly'),
56 'select' => array('size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'treeConfig'),
57 'group' => array('size', 'autoSizeMax', 'max_size', 'show_thumbs', 'maxitems', 'minitems', 'disable_controls', 'readOnly'),
58 'inline' => array('appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label', 'readOnly'),
59 );
60
61 /**
62 * @var array Cache of getLanguageIcon()
63 */
64 static protected $cachedLanguageFlag = array();
65
66 /**
67 * Overrides the TCA field configuration by TSconfig settings.
68 *
69 * Example TSconfig: TCEform.<table>.<field>.config.appearance.useSortable = 1
70 * This overrides the setting in $GLOBALS['TCA'][<table>]['columns'][<field>]['config']['appearance']['useSortable'].
71 *
72 * @param array $fieldConfig $GLOBALS['TCA'] field configuration
73 * @param array $TSconfig TSconfig
74 * @return array Changed TCA field configuration
75 * @internal
76 */
77 static public function overrideFieldConf($fieldConfig, $TSconfig) {
78 if (is_array($TSconfig)) {
79 $TSconfig = GeneralUtility::removeDotsFromTS($TSconfig);
80 $type = $fieldConfig['type'];
81 if (is_array($TSconfig['config']) && is_array(static::$allowOverrideMatrix[$type])) {
82 // Check if the keys in TSconfig['config'] are allowed to override TCA field config:
83 foreach ($TSconfig['config'] as $key => $_) {
84 if (!in_array($key, static::$allowOverrideMatrix[$type], TRUE)) {
85 unset($TSconfig['config'][$key]);
86 }
87 }
88 // Override $GLOBALS['TCA'] field config by remaining TSconfig['config']:
89 if (!empty($TSconfig['config'])) {
90 ArrayUtility::mergeRecursiveWithOverrule($fieldConfig, $TSconfig['config']);
91 }
92 }
93 }
94 return $fieldConfig;
95 }
96
97 /**
98 * Initializes language icons etc.
99 *
100 * @param string $table Table name
101 * @param array $row Record
102 * @param string $sys_language_uid Sys language uid OR ISO language code prefixed with "v", eg. "vDA
103 * @return string
104 * @internal
105 */
106 static public function getLanguageIcon($table, $row, $sys_language_uid) {
107 $mainKey = $table . ':' . $row['uid'];
108 if (!isset(static::$cachedLanguageFlag[$mainKey])) {
109 BackendUtility::fixVersioningPid($table, $row);
110 list($tscPID) = BackendUtility::getTSCpidCached($table, $row['uid'], $row['pid']);
111 /** @var $t8Tools TranslationConfigurationProvider */
112 $t8Tools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
113 static::$cachedLanguageFlag[$mainKey] = $t8Tools->getSystemLanguages($tscPID);
114 }
115 // Convert sys_language_uid to sys_language_uid if input was in fact a string (ISO code expected then)
116 if (!MathUtility::canBeInterpretedAsInteger($sys_language_uid)) {
117 foreach (static::$cachedLanguageFlag[$mainKey] as $rUid => $cD) {
118 if ('v' . $cD['ISOcode'] === $sys_language_uid) {
119 $sys_language_uid = $rUid;
120 }
121 }
122 }
123 $out = '';
124 if (static::$cachedLanguageFlag[$mainKey][$sys_language_uid]['flagIcon'] && static::$cachedLanguageFlag[$mainKey][$sys_language_uid]['flagIcon'] != 'empty-empty') {
125 $out .= IconUtility::getSpriteIcon(static::$cachedLanguageFlag[$mainKey][$sys_language_uid]['flagIcon']);
126 $out .= '&nbsp;';
127 } elseif (static::$cachedLanguageFlag[$mainKey][$sys_language_uid]['title']) {
128 $out .= '[' . static::$cachedLanguageFlag[$mainKey][$sys_language_uid]['title'] . ']';
129 $out .= '&nbsp;';
130 }
131 return $out;
132 }
133
134 /**
135 * Returns TSconfig for given table and row
136 *
137 * @param string $table The table name
138 * @param array $row The table row - Must at least contain the "uid" value, even if "NEW..." string.
139 * The "pid" field is important as well, negative values will be interpreted as pointing to a record from the same table.
140 * @param string $field Optionally specify the field name as well. In that case the TSconfig for this field is returned.
141 * @return mixed The TSconfig values - probably in an array
142 * @internal
143 */
144 static public function getTSconfigForTableRow($table, $row, $field = '') {
145 static $cache;
146 if (is_null($cache)) {
147 $cache = array();
148 }
149 $cacheIdentifier = $table . ':' . $row['uid'];
150 if (!isset($cache[$cacheIdentifier])) {
151 $cache[$cacheIdentifier] = BackendUtility::getTCEFORM_TSconfig($table, $row);
152 }
153 if ($field) {
154 return $cache[$cacheIdentifier][$field];
155 }
156 return $cache[$cacheIdentifier];
157 }
158
159 /**
160 * Extracting values from a value/label list (as made by transferData class)
161 *
162 * @param array $itemFormElValue Values in an array
163 * @return array Input string exploded with comma and for each value only the label part is set in the array. Keys are numeric
164 * @internal
165 */
166 static public function extractValuesOnlyFromValueLabelList($itemFormElValue) {
167 // Get values of selected items:
168 $itemArray = GeneralUtility::trimExplode(',', $itemFormElValue, TRUE);
169 foreach ($itemArray as $tk => $tv) {
170 $tvP = explode('|', $tv, 2);
171 $tvP[0] = rawurldecode($tvP[0]);
172 $itemArray[$tk] = $tvP[0];
173 }
174 return $itemArray;
175 }
176
177 /**
178 * Get icon (for example for selector boxes)
179 *
180 * @param string $icon Icon reference
181 * @return array Array with two values; the icon file reference, the icon file information array (getimagesize())
182 * @internal
183 */
184 static public function getIcon($icon) {
185 $selIconInfo = FALSE;
186 if (substr($icon, 0, 4) == 'EXT:') {
187 $file = GeneralUtility::getFileAbsFileName($icon);
188 if ($file) {
189 $file = PathUtility::stripPathSitePrefix($file);
190 $selIconFile = '../' . $file;
191 $selIconInfo = @getimagesize((PATH_site . $file));
192 } else {
193 $selIconFile = '';
194 }
195 } elseif (substr($icon, 0, 3) == '../') {
196 $selIconFile = GeneralUtility::resolveBackPath($icon);
197 if (is_file(PATH_site . GeneralUtility::resolveBackPath(substr($icon, 3)))) {
198 $selIconInfo = getimagesize((PATH_site . GeneralUtility::resolveBackPath(substr($icon, 3))));
199 }
200 } elseif (substr($icon, 0, 4) == 'ext/' || substr($icon, 0, 7) == 'sysext/') {
201 $selIconFile = $icon;
202 if (is_file(PATH_typo3 . $icon)) {
203 $selIconInfo = getimagesize(PATH_typo3 . $icon);
204 }
205 } else {
206 $selIconFile = IconUtility::skinImg('', 'gfx/' . $icon, '', 1);
207 $iconPath = $selIconFile;
208 if (is_file(PATH_typo3 . $iconPath)) {
209 $selIconInfo = getimagesize(PATH_typo3 . $iconPath);
210 }
211 }
212 if ($selIconInfo === FALSE) {
213 // Unset to empty string if icon is not available
214 $selIconFile = '';
215 }
216 return array($selIconFile, $selIconInfo);
217 }
218
219 /**
220 * Renders the $icon, supports a filename for skinImg or sprite-icon-name
221 *
222 * @param string $icon The icon passed, could be a file-reference or a sprite Icon name
223 * @param string $alt Alt attribute of the icon returned
224 * @param string $title Title attribute of the icon return
225 * @return string A tag representing to show the asked icon
226 * @internal
227 */
228 static public function getIconHtml($icon, $alt = '', $title = '') {
229 $iconArray = static::getIcon($icon);
230 if (!empty($iconArray[0]) && is_file(GeneralUtility::resolveBackPath(PATH_typo3 . PATH_typo3_mod . $iconArray[0]))) {
231 return '<img src="' . $iconArray[0] . '" alt="' . $alt . '" ' . ($title ? 'title="' . $title . '"' : '') . ' />';
232 } else {
233 return IconUtility::getSpriteIcon($icon, array('alt' => $alt, 'title' => $title));
234 }
235 }
236
237 /**
238 * Creates style attribute content for option tags in a selector box, primarily setting
239 * it up to show the icon of an element as background image (works in mozilla)
240 *
241 * @param string $iconString Icon string for option item
242 * @return string Style attribute content, if any
243 * @internal
244 */
245 static public function optionTagStyle($iconString) {
246 if (!$iconString) {
247 return '';
248 }
249 list($selIconFile, $selIconInfo) = static::getIcon($iconString);
250 if (empty($selIconFile)) {
251 // Skip background style if image is unavailable
252 return '';
253 }
254 $padLeft = $selIconInfo[0] + 4;
255 if ($padLeft >= 18 && $padLeft <= 24) {
256 // In order to get the same padding for all option tags even if icon sizes differ a little,
257 // set it to 22 if it was between 18 and 24 pixels
258 $padLeft = 22;
259 }
260 $padTop = MathUtility::forceIntegerInRange(($selIconInfo[1] - 12) / 2, 0);
261 $styleAttr = 'background: #fff url(' . $selIconFile . ') 0% 50% no-repeat; height: '
262 . MathUtility::forceIntegerInRange(($selIconInfo[1] + 2 - $padTop), 0)
263 . 'px; padding-top: ' . $padTop . 'px; padding-left: ' . $padLeft . 'px;';
264 return $styleAttr;
265 }
266
267 /**
268 * Initialize item array (for checkbox, selectorbox, radio buttons)
269 * Will resolve the label value.
270 *
271 * @param array $fieldValue The "columns" array for the field (from TCA)
272 * @return array An array of arrays with three elements; label, value, icon
273 * @internal
274 */
275 static public function initItemArray($fieldValue) {
276 $languageService = static::getLanguageService();
277 $items = array();
278 if (is_array($fieldValue['config']['items'])) {
279 foreach ($fieldValue['config']['items'] as $itemValue) {
280 $items[] = array($languageService->sL($itemValue[0]), $itemValue[1], $itemValue[2]);
281 }
282 }
283 return $items;
284 }
285
286 /**
287 * Merges items into an item-array, optionally with an icon
288 * example:
289 * TCEFORM.pages.doktype.addItems.13 = My Label
290 * TCEFORM.pages.doktype.addItems.13.icon = EXT:t3skin/icons/gfx/i/pages.gif
291 *
292 * @param array $items The existing item array
293 * @param array $iArray An array of items to add. NOTICE: The keys are mapped to values, and the values and mapped to be labels. No possibility of adding an icon.
294 * @return array The updated $item array
295 * @internal
296 */
297 static public function addItems($items, $iArray) {
298 $languageService = static::getLanguageService();
299 if (is_array($iArray)) {
300 foreach ($iArray as $value => $label) {
301 // if the label is an array (that means it is a subelement
302 // like "34.icon = mylabel.png", skip it (see its usage below)
303 if (is_array($label)) {
304 continue;
305 }
306 // check if the value "34 = mylabel" also has a "34.icon = myimage.png"
307 if (isset($iArray[$value . '.']) && $iArray[$value . '.']['icon']) {
308 $icon = $iArray[$value . '.']['icon'];
309 } else {
310 $icon = '';
311 }
312 $items[] = array($languageService->sL($label), $value, $icon);
313 }
314 }
315 return $items;
316 }
317
318 /**
319 * Collects the items for a select field by reading the configured
320 * select items from the configuration and / or by collecting them
321 * from a foreign table.
322 *
323 * @param string $table The table name of the record
324 * @param string $fieldName The select field name
325 * @param array $row The record data array where the value(s) for the field can be found
326 * @param array $PA An array with additional configuration options.
327 * @return array
328 */
329 static public function getSelectItems($table, $fieldName, array $row, array $PA) {
330 $config = $PA['fieldConf']['config'];
331
332 // Getting the selector box items from the system
333 $selectItems = FormEngineUtility::addSelectOptionsToItemArray(
334 FormEngineUtility::initItemArray($PA['fieldConf']),
335 $PA['fieldConf'],
336 FormEngineUtility::getTSconfigForTableRow($table, $row),
337 $fieldName
338 );
339
340 // Possibly filter some items:
341 $selectItems = ArrayUtility::keepItemsInArray(
342 $selectItems,
343 $PA['fieldTSConfig']['keepItems'],
344 function ($value) {
345 return $value[1];
346 }
347 );
348
349 // Possibly add some items:
350 $selectItems = FormEngineUtility::addItems($selectItems, $PA['fieldTSConfig']['addItems.']);
351
352 // Process items by a user function:
353 if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
354 $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
355 $selectItems = $dataPreprocessor->procItems($selectItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $fieldName);
356 }
357
358 // Possibly remove some items:
359 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
360 foreach ($selectItems as $selectItemIndex => $selectItem) {
361
362 // Checking languages and authMode:
363 $languageDeny = FALSE;
364 $beUserAuth = static::getBackendUserAuthentication();
365 if (
366 !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])
367 && $GLOBALS['TCA'][$table]['ctrl']['languageField'] === $fieldName
368 && !$beUserAuth->checkLanguageAccess($selectItem[1])
369 ) {
370 $languageDeny = TRUE;
371 }
372
373 $authModeDeny = FALSE;
374 if (
375 ($config['type'] === 'select')
376 && $config['authMode']
377 && !$beUserAuth->checkAuthMode($table, $fieldName, $selectItem[1], $config['authMode'])
378 ) {
379 $authModeDeny = TRUE;
380 }
381
382 if (in_array($selectItem[1], $removeItems) || $languageDeny || $authModeDeny) {
383 unset($selectItems[$selectItemIndex]);
384 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$selectItem[1]])) {
385 $selectItems[$selectItemIndex][0] = htmlspecialchars(static::getLanguageService()->sL($PA['fieldTSConfig']['altLabels.'][$selectItem[1]]));
386 }
387
388 // Removing doktypes with no access:
389 if (($table === 'pages' || $table === 'pages_language_overlay') && $fieldName === 'doktype') {
390 if (!($beUserAuth->isAdmin() || GeneralUtility::inList($beUserAuth->groupData['pagetypes_select'], $selectItem[1]))) {
391 unset($selectItems[$selectItemIndex]);
392 }
393 }
394 }
395
396 return $selectItems;
397 }
398
399 /**
400 * Add selector box items of more exotic kinds.
401 *
402 * @param array $items The array of items (label,value,icon)
403 * @param array $fieldValue The "columns" array for the field (from TCA)
404 * @param array $TSconfig TSconfig for the table/row
405 * @param string $field The fieldname
406 * @return array The $items array modified.
407 * @internal
408 */
409 static public function addSelectOptionsToItemArray($items, $fieldValue, $TSconfig, $field) {
410 $languageService = static::getLanguageService();
411
412 // Values from foreign tables:
413 if ($fieldValue['config']['foreign_table']) {
414 $items = static::foreignTable($items, $fieldValue, $TSconfig, $field);
415 if ($fieldValue['config']['neg_foreign_table']) {
416 $items = static::foreignTable($items, $fieldValue, $TSconfig, $field, 1);
417 }
418 }
419
420 // Values from a file folder:
421 if ($fieldValue['config']['fileFolder']) {
422 $fileFolder = GeneralUtility::getFileAbsFileName($fieldValue['config']['fileFolder']);
423 if (@is_dir($fileFolder)) {
424 // Configurations:
425 $extList = $fieldValue['config']['fileFolder_extList'];
426 $recursivityLevels = isset($fieldValue['config']['fileFolder_recursions'])
427 ? MathUtility::forceIntegerInRange($fieldValue['config']['fileFolder_recursions'], 0, 99)
428 : 99;
429 // Get files:
430 $fileFolder = rtrim($fileFolder, '/') . '/';
431 $fileArr = GeneralUtility::getAllFilesAndFoldersInPath(array(), $fileFolder, $extList, 0, $recursivityLevels);
432 $fileArr = GeneralUtility::removePrefixPathFromList($fileArr, $fileFolder);
433 foreach ($fileArr as $fileRef) {
434 $fI = pathinfo($fileRef);
435 $icon = GeneralUtility::inList('gif,png,jpeg,jpg', strtolower($fI['extension']))
436 ? '../' . PathUtility::stripPathSitePrefix($fileFolder) . $fileRef
437 : '';
438 $items[] = array(
439 $fileRef,
440 $fileRef,
441 $icon
442 );
443 }
444 }
445 }
446
447 // If 'special' is configured:
448 if ($fieldValue['config']['special']) {
449 switch ($fieldValue['config']['special']) {
450 case 'tables':
451 foreach ($GLOBALS['TCA'] as $theTableNames => $_) {
452 if (!$GLOBALS['TCA'][$theTableNames]['ctrl']['adminOnly']) {
453 // Icon:
454 $icon = IconUtility::mapRecordTypeToSpriteIconName($theTableNames, array());
455 // Add help text
456 $helpText = array();
457 $languageService->loadSingleTableDescription($theTableNames);
458 $helpTextArray = $GLOBALS['TCA_DESCR'][$theTableNames]['columns'][''];
459 if (!empty($helpTextArray['description'])) {
460 $helpText['description'] = $helpTextArray['description'];
461 }
462 // Item configuration:
463 $items[] = array(
464 $languageService->sL($GLOBALS['TCA'][$theTableNames]['ctrl']['title']),
465 $theTableNames,
466 $icon,
467 $helpText
468 );
469 }
470 }
471 break;
472 case 'pagetypes':
473 $theTypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
474 foreach ($theTypes as $theTypeArrays) {
475 // Icon:
476 $icon = 'empty-empty';
477 if ($theTypeArrays[1] != '--div--') {
478 $icon = IconUtility::mapRecordTypeToSpriteIconName('pages', array('doktype' => $theTypeArrays[1]));
479 }
480 // Item configuration:
481 $items[] = array(
482 $languageService->sL($theTypeArrays[0]),
483 $theTypeArrays[1],
484 $icon
485 );
486 }
487 break;
488 case 'exclude':
489 $theTypes = BackendUtility::getExcludeFields();
490 foreach ($theTypes as $theTypeArrays) {
491 list($theTable, $theFullField) = explode(':', $theTypeArrays[1]);
492 // If the field comes from a FlexForm, the syntax is more complex
493 $theFieldParts = explode(';', $theFullField);
494 $theField = array_pop($theFieldParts);
495 // Add header if not yet set for table:
496 if (!array_key_exists($theTable, $items)) {
497 $icon = IconUtility::mapRecordTypeToSpriteIconName($theTable, array());
498 $items[$theTable] = array(
499 $languageService->sL($GLOBALS['TCA'][$theTable]['ctrl']['title']),
500 '--div--',
501 $icon
502 );
503 }
504 // Add help text
505 $helpText = array();
506 $languageService->loadSingleTableDescription($theTable);
507 $helpTextArray = $GLOBALS['TCA_DESCR'][$theTable]['columns'][$theFullField];
508 if (!empty($helpTextArray['description'])) {
509 $helpText['description'] = $helpTextArray['description'];
510 }
511 // Item configuration:
512 $items[] = array(
513 rtrim($languageService->sL($GLOBALS['TCA'][$theTable]['columns'][$theField]['label']), ':') . ' (' . $theField . ')',
514 $theTypeArrays[1],
515 'empty-empty',
516 $helpText
517 );
518 }
519 break;
520 case 'explicitValues':
521 $theTypes = BackendUtility::getExplicitAuthFieldValues();
522 // Icons:
523 $icons = array(
524 'ALLOW' => 'status-status-permission-granted',
525 'DENY' => 'status-status-permission-denied'
526 );
527 // Traverse types:
528 foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
529 if (is_array($theTypeArrays['items'])) {
530 // Add header:
531 $items[] = array(
532 $theTypeArrays['tableFieldLabel'],
533 '--div--'
534 );
535 // Traverse options for this field:
536 foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
537 // Add item to be selected:
538 $items[] = array(
539 '[' . $itemContent[2] . '] ' . $itemContent[1],
540 $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
541 $icons[$itemContent[0]]
542 );
543 }
544 }
545 }
546 break;
547 case 'languages':
548 $items = array_merge($items, BackendUtility::getSystemLanguages());
549 break;
550 case 'custom':
551 // Initialize:
552 $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
553 if (is_array($customOptions)) {
554 foreach ($customOptions as $coKey => $coValue) {
555 if (is_array($coValue['items'])) {
556 // Add header:
557 $items[] = array(
558 $languageService->sL($coValue['header']),
559 '--div--'
560 );
561 // Traverse items:
562 foreach ($coValue['items'] as $itemKey => $itemCfg) {
563 // Icon:
564 if ($itemCfg[1]) {
565 list($icon) = FormEngineUtility::getIcon($itemCfg[1]);
566 } else {
567 $icon = 'empty-empty';
568 }
569 // Add help text
570 $helpText = array();
571 if (!empty($itemCfg[2])) {
572 $helpText['description'] = $languageService->sL($itemCfg[2]);
573 }
574 // Add item to be selected:
575 $items[] = array(
576 $languageService->sL($itemCfg[0]),
577 $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
578 $icon,
579 $helpText
580 );
581 }
582 }
583 }
584 }
585 break;
586 case 'modListGroup':
587
588 case 'modListUser':
589 $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
590 $loadModules->load($GLOBALS['TBE_MODULES']);
591 $modList = $fieldValue['config']['special'] == 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
592 if (is_array($modList)) {
593 foreach ($modList as $theMod) {
594 // Icon:
595 $icon = $languageService->moduleLabels['tabs_images'][$theMod . '_tab'];
596 if ($icon) {
597 $icon = '../' . PathUtility::stripPathSitePrefix($icon);
598 }
599 // Add help text
600 $helpText = array(
601 'title' => $languageService->moduleLabels['labels'][$theMod . '_tablabel'],
602 'description' => $languageService->moduleLabels['labels'][$theMod . '_tabdescr']
603 );
604
605 $label = '';
606 // Add label for main module:
607 $pp = explode('_', $theMod);
608 if (count($pp) > 1) {
609 $label .= $languageService->moduleLabels['tabs'][($pp[0] . '_tab')] . '>';
610 }
611 // Add modules own label now:
612 $label .= $languageService->moduleLabels['tabs'][$theMod . '_tab'];
613
614 // Item configuration:
615 $items[] = array($label, $theMod, $icon, $helpText);
616 }
617 }
618 break;
619 }
620 }
621
622 return $items;
623 }
624
625 /**
626 * Extracts FlexForm parts of a form element name like
627 * data[table][uid][field][sDEF][lDEF][FlexForm][vDEF]
628 * Helper method used in inline
629 *
630 * @param string $formElementName The form element name
631 * @return array|NULL
632 * @internal
633 */
634 static public function extractFlexFormParts($formElementName) {
635 $flexFormParts = NULL;
636
637 $matches = array();
638
639 if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
640 $flexFormParts = GeneralUtility::trimExplode(
641 '][',
642 trim($matches[1], '[]')
643 );
644 }
645
646 return $flexFormParts;
647 }
648
649 /**
650 * Get inlineFirstPid from a given objectId string
651 *
652 * @param string $domObjectId The id attribute of an element
653 * @return integer|NULL Pid or null
654 * @internal
655 */
656 static public function getInlineFirstPidFromDomObjectId($domObjectId) {
657 // Substitute FlexForm addition and make parsing a bit easier
658 $domObjectId = str_replace('---', ':', $domObjectId);
659 // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
660 $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
661 if (preg_match($pattern, $domObjectId, $match)) {
662 return $match[1];
663 }
664 return NULL;
665 }
666
667 /**
668 * Adds / adapts some general options of main TCA config for inline usage
669 *
670 * @param array $config TCA field configuration
671 * @return array Modified configuration
672 * @internal
673 */
674 static public function mergeInlineConfiguration($config) {
675 // Init appearance if not set:
676 if (!isset($config['appearance']) || !is_array($config['appearance'])) {
677 $config['appearance'] = array();
678 }
679 // Set the position/appearance of the "Create new record" link:
680 if (
681 isset($config['foreign_selector'])
682 && $config['foreign_selector']
683 && (!isset($config['appearance']['useCombination']) || !$config['appearance']['useCombination'])
684 ) {
685 $config['appearance']['levelLinksPosition'] = 'none';
686 } elseif (
687 !isset($config['appearance']['levelLinksPosition'])
688 || !in_array($config['appearance']['levelLinksPosition'], array('top', 'bottom', 'both', 'none'))
689 ) {
690 $config['appearance']['levelLinksPosition'] = 'top';
691 }
692 // Defines which controls should be shown in header of each record:
693 $enabledControls = array(
694 'info' => TRUE,
695 'new' => TRUE,
696 'dragdrop' => TRUE,
697 'sort' => TRUE,
698 'hide' => TRUE,
699 'delete' => TRUE,
700 'localize' => TRUE
701 );
702 if (isset($config['appearance']['enabledControls']) && is_array($config['appearance']['enabledControls'])) {
703 $config['appearance']['enabledControls'] = array_merge($enabledControls, $config['appearance']['enabledControls']);
704 } else {
705 $config['appearance']['enabledControls'] = $enabledControls;
706 }
707 return $config;
708 }
709
710 /**
711 * Determine the configuration and the type of a record selector.
712 * This is a helper method for inline / IRRE handling
713 *
714 * @param array $conf TCA configuration of the parent(!) field
715 * @param string $field Field name
716 * @return array Associative array with the keys 'PA' and 'type', both are FALSE if the selector was not valid.
717 * @internal
718 */
719 static public function getInlinePossibleRecordsSelectorConfig($conf, $field = '') {
720 $foreign_table = $conf['foreign_table'];
721 $foreign_selector = $conf['foreign_selector'];
722 $PA = FALSE;
723 $type = FALSE;
724 $table = FALSE;
725 $selector = FALSE;
726 if ($field) {
727 $PA = array();
728 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field];
729 if ($PA['fieldConf'] && $conf['foreign_selector_fieldTcaOverride']) {
730 ArrayUtility::mergeRecursiveWithOverrule($PA['fieldConf'], $conf['foreign_selector_fieldTcaOverride']);
731 }
732 $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $field);
733 $config = $PA['fieldConf']['config'];
734 // Determine type of Selector:
735 $type = static::getInlinePossibleRecordsSelectorType($config);
736 // Return table on this level:
737 $table = $type === 'select' ? $config['foreign_table'] : $config['allowed'];
738 // Return type of the selector if foreign_selector is defined and points to the same field as in $field:
739 if ($foreign_selector && $foreign_selector == $field && $type) {
740 $selector = $type;
741 }
742 }
743 return array(
744 'PA' => $PA,
745 'type' => $type,
746 'table' => $table,
747 'selector' => $selector
748 );
749 }
750
751 /**
752 * Determine the type of a record selector, e.g. select or group/db.
753 *
754 * @param array $config TCE configuration of the selector
755 * @return mixed The type of the selector, 'select' or 'groupdb' - FALSE not valid
756 * @internal
757 */
758 static protected function getInlinePossibleRecordsSelectorType($config) {
759 $type = FALSE;
760 if ($config['type'] === 'select') {
761 $type = 'select';
762 } elseif ($config['type'] === 'group' && $config['internal_type'] === 'db') {
763 $type = 'groupdb';
764 }
765 return $type;
766 }
767
768 /**
769 * Update expanded/collapsed states on new inline records if any.
770 *
771 * @param array $uc The uc array to be processed and saved (by reference)
772 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tce Instance of FormEngine that saved data before
773 * @return void
774 * @internal
775 */
776 static public function updateInlineView(&$uc, $tce) {
777 $backendUser = static::getBackendUserAuthentication();
778 if (isset($uc['inlineView']) && is_array($uc['inlineView'])) {
779 $inlineView = (array)unserialize($backendUser->uc['inlineView']);
780 foreach ($uc['inlineView'] as $topTable => $topRecords) {
781 foreach ($topRecords as $topUid => $childElements) {
782 foreach ($childElements as $childTable => $childRecords) {
783 $uids = array_keys($tce->substNEWwithIDs_table, $childTable);
784 if (!empty($uids)) {
785 $newExpandedChildren = array();
786 foreach ($childRecords as $childUid => $state) {
787 if ($state && in_array($childUid, $uids)) {
788 $newChildUid = $tce->substNEWwithIDs[$childUid];
789 $newExpandedChildren[] = $newChildUid;
790 }
791 }
792 // Add new expanded child records to UC (if any):
793 if (!empty($newExpandedChildren)) {
794 $inlineViewCurrent = &$inlineView[$topTable][$topUid][$childTable];
795 if (is_array($inlineViewCurrent)) {
796 $inlineViewCurrent = array_unique(array_merge($inlineViewCurrent, $newExpandedChildren));
797 } else {
798 $inlineViewCurrent = $newExpandedChildren;
799 }
800 }
801 }
802 }
803 }
804 }
805 $backendUser->uc['inlineView'] = serialize($inlineView);
806 $backendUser->writeUC();
807 }
808 }
809
810 /**
811 * Gets an array with the uids of related records out of a list of items.
812 * This list could contain more information than required. This methods just
813 * extracts the uids.
814 *
815 * @param string $itemList The list of related child records
816 * @return array An array with uids
817 * @internal
818 */
819 static public function getInlineRelatedRecordsUidArray($itemList) {
820 $itemArray = GeneralUtility::trimExplode(',', $itemList, TRUE);
821 // Perform modification of the selected items array:
822 foreach ($itemArray as &$value) {
823 $parts = explode('|', $value, 2);
824 $value = $parts[0];
825 }
826 unset($value);
827 return $itemArray;
828 }
829
830 /**
831 * Adds records from a foreign table (for selector boxes). Helper for addSelectOptionsToItemArray()
832 *
833 * @param array $items The array of items (label,value,icon)
834 * @param array $fieldValue The 'columns' array for the field (from TCA)
835 * @param array $TSconfig TSconfig for the table/row
836 * @param string $field The fieldname
837 * @param bool $pFFlag If set, then we are fetching the 'neg_' foreign tables.
838 * @return array The $items array modified.
839 * @internal
840 */
841 static protected function foreignTable($items, $fieldValue, $TSconfig, $field, $pFFlag = FALSE) {
842 $languageService = static::getLanguageService();
843 $db = static::getDatabaseConnection();
844
845 // Init:
846 $pF = $pFFlag ? 'neg_' : '';
847 $f_table = $fieldValue['config'][$pF . 'foreign_table'];
848 $uidPre = $pFFlag ? '-' : '';
849 // Exec query:
850 $res = BackendUtility::exec_foreign_table_where_query($fieldValue, $field, $TSconfig, $pF);
851 // Perform error test
852 if ($db->sql_error()) {
853 $msg = htmlspecialchars($db->sql_error());
854 $msg .= '<br />' . LF;
855 $msg .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch');
856 $msgTitle = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch_title');
857 /** @var $flashMessage FlashMessage */
858 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, TRUE);
859 /** @var $flashMessageService FlashMessageService */
860 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
861 /** @var $defaultFlashMessageQueue FlashMessageQueue */
862 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
863 $defaultFlashMessageQueue->enqueue($flashMessage);
864 return array();
865 }
866 // Get label prefix.
867 $lPrefix = $languageService->sL($fieldValue['config'][$pF . 'foreign_table_prefix']);
868 // Get icon field + path if any:
869 $iField = $GLOBALS['TCA'][$f_table]['ctrl']['selicon_field'];
870 $iPath = trim($GLOBALS['TCA'][$f_table]['ctrl']['selicon_field_path']);
871 // Traverse the selected rows to add them:
872 while ($row = $db->sql_fetch_assoc($res)) {
873 BackendUtility::workspaceOL($f_table, $row);
874 if (is_array($row)) {
875 // Prepare the icon if available:
876 if ($iField && $iPath && $row[$iField]) {
877 $iParts = GeneralUtility::trimExplode(',', $row[$iField], TRUE);
878 $icon = '../' . $iPath . '/' . trim($iParts[0]);
879 } elseif (GeneralUtility::inList('singlebox,checkbox', $fieldValue['config']['renderMode'])) {
880 $icon = IconUtility::mapRecordTypeToSpriteIconName($f_table, $row);
881 } else {
882 $icon = '';
883 }
884 // Add the item:
885 $items[] = array(
886 $lPrefix . htmlspecialchars(BackendUtility::getRecordTitle($f_table, $row)),
887 $uidPre . $row['uid'],
888 $icon
889 );
890 }
891 }
892 $db->sql_free_result($res);
893 return $items;
894 }
895
896 /**
897 * @return LanguageService
898 */
899 static protected function getLanguageService() {
900 return $GLOBALS['LANG'];
901 }
902
903 /**
904 * @return DatabaseConnection
905 */
906 static protected function getDatabaseConnection() {
907 return $GLOBALS['TYPO3_DB'];
908 }
909
910 /**
911 * @return BackendUserAuthentication
912 */
913 static protected function getBackendUserAuthentication() {
914 return $GLOBALS['BE_USER'];
915 }
916
917 }