441512610004d91165b9433d7cef178e9bf89fcd
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Container / AbstractContainer.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\Utility\GeneralUtility;
18 use TYPO3\CMS\Backend\Form\AbstractNode;
19 use TYPO3\CMS\Backend\Form\ElementConditionMatcher;
20 use TYPO3\CMS\Backend\Utility\IconUtility;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Backend\Template\DocumentTemplate;
23 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
24 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26
27 /**
28 * Abstract container has various methods used by the container classes
29 */
30 abstract class AbstractContainer extends AbstractNode {
31
32 /**
33 * Array where records in the default language are stored. (processed by transferdata)
34 *
35 * @var array
36 */
37 protected $defaultLanguageData = array();
38
39 /**
40 * Array where records in the default language are stored (raw without any processing. used for making diff).
41 * This is the unserialized content of configured TCA ['ctrl']['transOrigDiffSourceField'] field, typically l18n_diffsource
42 *
43 * @var array
44 */
45 protected $defaultLanguageDataDiff = array();
46
47 /**
48 * Contains row data of "additional" language overlays
49 * array(
50 * $table:$uid => array(
51 * $additionalPreviewLanguageUid => $rowData
52 * )
53 * )
54 *
55 * @var array
56 */
57 protected $additionalPreviewLanguageData = array();
58
59 /**
60 * Calculate and return the current type value of a record
61 *
62 * @param string $table The table name. MUST be in $GLOBALS['TCA']
63 * @param array $row The row from the table, should contain at least the "type" field, if applicable.
64 * @return string Return the "type" value for this record, ready to pick a "types" configuration from the $GLOBALS['TCA'] array.
65 * @throws \RuntimeException
66 */
67 protected function getRecordTypeValue($table, array $row) {
68 $typeNum = 0;
69 $field = $GLOBALS['TCA'][$table]['ctrl']['type'];
70 if ($field) {
71 if (strpos($field, ':') !== FALSE) {
72 list($pointerField, $foreignTypeField) = explode(':', $field);
73 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
74 $relationType = $fieldConfig['type'];
75 if ($relationType === 'select') {
76 $foreignUid = $row[$pointerField];
77 $foreignTable = $fieldConfig['foreign_table'];
78 } elseif ($relationType === 'group') {
79 $values = FormEngineUtility::extractValuesOnlyFromValueLabelList($row[$pointerField]);
80 list(, $foreignUid) = GeneralUtility::revExplode('_', $values[0], 2);
81 $allowedTables = explode(',', $fieldConfig['allowed']);
82 // Always take the first configured table.
83 $foreignTable = $allowedTables[0];
84 } else {
85 throw new \RuntimeException('TCA Foreign field pointer fields are only allowed to be used with group or select field types.', 1325861239);
86 }
87 if ($foreignUid) {
88 $foreignRow = BackendUtility::getRecord($foreignTable, $foreignUid, $foreignTypeField);
89 $this->registerDefaultLanguageData($foreignTable, $foreignRow);
90 if ($foreignRow[$foreignTypeField]) {
91 $foreignTypeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
92 $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($foreignTable, $foreignRow, $foreignTypeField, $foreignTypeFieldConfig);
93 }
94 }
95 } else {
96 $typeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
97 $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($table, $row, $field, $typeFieldConfig);
98 }
99 }
100 if (empty($typeNum)) {
101 // If that value is an empty string, set it to "0" (zero)
102 $typeNum = 0;
103 }
104 // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
105 if (!$GLOBALS['TCA'][$table]['types'][$typeNum]) {
106 $typeNum = $GLOBALS['TCA'][$table]['types']['0'] ? 0 : 1;
107 }
108 // Force to string. Necessary for eg '-1' to be recognized as a type value.
109 return (string)$typeNum;
110 }
111
112 /**
113 * Producing an array of field names NOT to display in the form,
114 * based on settings from subtype_value_field, bitmask_excludelist_bits etc.
115 * Notice, this list is in NO way related to the "excludeField" flag
116 *
117 * @param string $table Table name, MUST be in $GLOBALS['TCA']
118 * @param array $row A record from table.
119 * @param string $typeNum A "type" pointer value, probably the one calculated based on the record array.
120 * @return array Array with field names as values. The field names are those which should NOT be displayed "anyways
121 */
122 protected function getExcludeElements($table, $row, $typeNum) {
123 $excludeElements = array();
124 // If a subtype field is defined for the type
125 if ($GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field']) {
126 $subTypeField = $GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field'];
127 if (trim($GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_excludelist'][$row[$subTypeField]])) {
128 $excludeElements = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_excludelist'][$row[$subTypeField]], TRUE);
129 }
130 }
131 // If a bitmask-value field has been configured, then find possible fields to exclude based on that:
132 if ($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_value_field']) {
133 $subTypeField = $GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_value_field'];
134 $sTValue = MathUtility::forceIntegerInRange($row[$subTypeField], 0);
135 if (is_array($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_excludelist_bits'])) {
136 foreach ($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_excludelist_bits'] as $bitKey => $eList) {
137 $bit = substr($bitKey, 1);
138 if (MathUtility::canBeInterpretedAsInteger($bit)) {
139 $bit = MathUtility::forceIntegerInRange($bit, 0, 30);
140 if ($bitKey[0] === '-' && !($sTValue & pow(2, $bit)) || $bitKey[0] === '+' && $sTValue & pow(2, $bit)) {
141 $excludeElements = array_merge($excludeElements, GeneralUtility::trimExplode(',', $eList, TRUE));
142 }
143 }
144 }
145 }
146 }
147 return $excludeElements;
148 }
149
150 /**
151 * The requested field value will be overridden with the data from the default
152 * language if the field is configured accordingly.
153 *
154 * @param string $table Table name of the record being edited
155 * @param array $row Record array of the record being edited in current language
156 * @param string $field Field name represented by $item
157 * @param array $fieldConf Content of $PA['fieldConf']
158 * @return string Unprocessed field value merged with default language data if needed
159 */
160 protected function overrideTypeWithValueFromDefaultLanguageRecord($table, array $row, $field, $fieldConf) {
161 $value = $row[$field];
162 if (is_array($this->defaultLanguageData[$table . ':' . $row['uid']])) {
163 if (
164 $fieldConf['l10n_mode'] === 'exclude'
165 || ($fieldConf['l10n_mode'] === 'mergeIfNotBlank' && trim($row[$field] === ''))
166 ) {
167 $value = $this->defaultLanguageData[$table . ':' . $row['uid']][$field];
168 }
169 }
170 return $value;
171 }
172
173 /**
174 * Return a list without excluded elements.
175 *
176 * @param array $fieldsArray Typically coming from types show item
177 * @param array $excludeElements Field names to be excluded
178 * @return array $fieldsArray without excluded elements
179 */
180 protected function removeExcludeElementsFromFieldArray(array $fieldsArray, array $excludeElements) {
181 $newFieldArray = array();
182 foreach ($fieldsArray as $fieldString) {
183 $fieldArray = $this->explodeSingleFieldShowItemConfiguration($fieldString);
184 $fieldName = $fieldArray['fieldName'];
185 // It doesn't make sense to exclude palettes and tabs
186 if (!in_array($fieldName, $excludeElements, TRUE) || $fieldName === '--palette--' || $fieldName === '--div--') {
187 $newFieldArray[] = $fieldString;
188 }
189 }
190 return $newFieldArray;
191 }
192
193
194 /**
195 * A single field of TCA 'types' 'showitem' can have four semicolon separated configuration options:
196 * fieldName: Name of the field to be found in TCA 'columns' section
197 * fieldLabel: An alternative field label
198 * paletteName: Name of a palette to be found in TCA 'palettes' section that is rendered after this field
199 * extra: Special configuration options of this field
200 *
201 * @param string $field Semicolon separated field configuration
202 * @throws \RuntimeException
203 * @return array
204 */
205 protected function explodeSingleFieldShowItemConfiguration($field) {
206 $fieldArray = GeneralUtility::trimExplode(';', $field);
207 if (empty($fieldArray[0])) {
208 throw new \RuntimeException('Field must not be empty', 1426448465);
209 }
210 return array(
211 'fieldName' => $fieldArray[0],
212 'fieldLabel' => $fieldArray[1] ?: NULL,
213 'paletteName' => $fieldArray[2] ?: NULL,
214 );
215 }
216
217 /**
218 * Will register data from original language records if the current record is a translation of another.
219 * The original data is shown with the edited record in the form.
220 * The information also includes possibly diff-views of what changed in the original record.
221 * Function called from outside (see alt_doc.php + quick edit) before rendering a form for a record
222 *
223 * @param string $table Table name of the record being edited
224 * @param array $rec Record array of the record being edited
225 * @return void
226 */
227 protected function registerDefaultLanguageData($table, $rec) {
228 // @todo: early return here if the arrays are already filled?
229
230 // Add default language:
231 if (
232 $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $rec[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
233 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
234 && (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0
235 ) {
236 $lookUpTable = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
237 ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
238 : $table;
239 // Get data formatted:
240 $this->defaultLanguageData[$table . ':' . $rec['uid']] = BackendUtility::getRecordWSOL(
241 $lookUpTable,
242 (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
243 );
244 // Get data for diff:
245 if ($GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']) {
246 $this->defaultLanguageDataDiff[$table . ':' . $rec['uid']] = unserialize($rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']]);
247 }
248 // If there are additional preview languages, load information for them also:
249 foreach ($this->globalOptions['additionalPreviewLanguages'] as $prL) {
250 /** @var $translationConfigurationProvider TranslationConfigurationProvider */
251 $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
252 $translationInfo = $translationConfigurationProvider->translationInfo($lookUpTable, (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], $prL['uid']);
253 if (is_array($translationInfo['translations']) && is_array($translationInfo['translations'][$prL['uid']])) {
254 $this->additionalPreviewLanguageData[$table . ':' . $rec['uid']][$prL['uid']] = BackendUtility::getRecordWSOL($table, (int)$translationInfo['translations'][$prL['uid']]['uid']);
255 }
256 }
257 }
258 }
259
260 /**
261 * Evaluate condition of flex forms
262 *
263 * @param string $displayCondition The condition to evaluate
264 * @param array $flexFormData Given data the condition is based on
265 * @param string $flexFormLanguage Flex form language key
266 * @return bool TRUE if condition matched
267 */
268 protected function evaluateFlexFormDisplayCondition($displayCondition, $flexFormData, $flexFormLanguage) {
269 $elementConditionMatcher = GeneralUtility::makeInstance(ElementConditionMatcher::class);
270
271 $splitCondition = GeneralUtility::trimExplode(':', $displayCondition);
272 $skipCondition = FALSE;
273 $fakeRow = array();
274 switch ($splitCondition[0]) {
275 case 'FIELD':
276 list($_sheetName, $fieldName) = GeneralUtility::trimExplode('.', $splitCondition[1]);
277 $fieldValue = $flexFormData[$_sheetName][$flexFormLanguage][$fieldName];
278 $splitCondition[1] = $fieldName;
279 $displayCondition = join(':', $splitCondition);
280 $fakeRow = array($fieldName => $fieldValue);
281 break;
282 case 'HIDE_FOR_NON_ADMINS':
283
284 case 'VERSION':
285
286 case 'HIDE_L10N_SIBLINGS':
287
288 case 'EXT':
289 break;
290 case 'REC':
291 $fakeRow = array('uid' => $this->globalOptions['databaseRow']['uid']);
292 break;
293 default:
294 $skipCondition = TRUE;
295 }
296 if ($skipCondition) {
297 return TRUE;
298 } else {
299 return $elementConditionMatcher->match($displayCondition, $fakeRow, 'vDEF');
300 }
301 }
302
303 /**
304 * Rendering preview output of a field value which is not shown as a form field but just outputted.
305 *
306 * @param string $value The value to output
307 * @param array $config Configuration for field.
308 * @param string $field Name of field.
309 * @return string HTML formatted output
310 */
311 protected function previewFieldValue($value, $config, $field = '') {
312 if ($config['config']['type'] === 'group' && ($config['config']['internal_type'] === 'file' || $config['config']['internal_type'] === 'file_reference')) {
313 // Ignore upload folder if internal_type is file_reference
314 if ($config['config']['internal_type'] === 'file_reference') {
315 $config['config']['uploadfolder'] = '';
316 }
317 $table = 'tt_content';
318 // Making the array of file items:
319 $itemArray = GeneralUtility::trimExplode(',', $value, TRUE);
320 // Showing thumbnails:
321 $thumbnail = '';
322 $imgs = array();
323 foreach ($itemArray as $imgRead) {
324 $imgParts = explode('|', $imgRead);
325 $imgPath = rawurldecode($imgParts[0]);
326 $rowCopy = array();
327 $rowCopy[$field] = $imgPath;
328 // Icon + click menu:
329 $absFilePath = GeneralUtility::getFileAbsFileName($config['config']['uploadfolder'] ? $config['config']['uploadfolder'] . '/' . $imgPath : $imgPath);
330 $fileInformation = pathinfo($imgPath);
331 $fileIcon = IconUtility::getSpriteIconForFile(
332 $imgPath,
333 array(
334 'title' => htmlspecialchars($fileInformation['basename'] . ($absFilePath && @is_file($absFilePath) ? ' (' . GeneralUtility::formatSize(filesize($absFilePath)) . 'bytes)' : ' - FILE NOT FOUND!'))
335 )
336 );
337 $imgs[] =
338 '<span class="text-nowrap">' .
339 BackendUtility::thumbCode(
340 $rowCopy,
341 $table,
342 $field,
343 '',
344 'thumbs.php',
345 $config['config']['uploadfolder'], 0, ' align="middle"'
346 ) .
347 ($absFilePath ? $this->getControllerDocumentTemplate()->wrapClickMenuOnIcon($fileIcon, $absFilePath, 0, 1, '', '+copy,info,edit,view') : $fileIcon) .
348 $imgPath .
349 '</span>';
350 }
351 return implode('<br />', $imgs);
352 } else {
353 return nl2br(htmlspecialchars($value));
354 }
355 }
356
357 /**
358 * @return DocumentTemplate
359 */
360 protected function getControllerDocumentTemplate() {
361 return $GLOBALS['SOBE']->doc;
362 }
363
364 }