‪TYPO3CMS  ‪main
SingleFieldContainer.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
25 
35 {
42  public function ‪render(): array
43  {
44  $backendUser = $this->‪getBackendUserAuthentication();
45  $resultArray = $this->‪initializeResultArray();
46 
47  $table = $this->data['tableName'];
48  $row = $this->data['databaseRow'];
49  $fieldName = $this->data['fieldName'];
50 
51  $parameterArray = [];
52  $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName];
53 
54  $isOverlay = false;
55 
56  // This field decides whether the current record is an overlay (as opposed to being a standalone record)
57  // Based on this decision we need to trigger field exclusion or special rendering (like readOnly)
58  if (isset($this->data['processedTca']['ctrl']['transOrigPointerField'])
59  && is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']] ?? null)
60  ) {
61  $parentValue = $row[$this->data['processedTca']['ctrl']['transOrigPointerField']];
63  $isOverlay = (bool)$parentValue;
64  } elseif (is_array($parentValue)) {
65  // This case may apply if the value has been converted to an array by the select or group data provider
66  $isOverlay = !empty($parentValue) ? (bool)$parentValue[0] : false;
67  } else {
68  throw new \InvalidArgumentException(
69  'The given value "' . $parentValue . '" for the original language field ' . $this->data['processedTca']['ctrl']['transOrigPointerField']
70  . ' of table ' . $table . ' is invalid.',
71  1470742770
72  );
73  }
74  }
75 
76  // A couple of early returns in case the field should not be rendered
77  $fieldIsExcluded = $parameterArray['fieldConf']['exclude'] ?? false;
78  $fieldNotExcludable = $backendUser->check('non_exclude_fields', $table . ':' . $fieldName);
79  $fieldExcludedFromTranslatedRecords = empty($parameterArray['fieldConf']['l10n_display']) && ($parameterArray['fieldConf']['l10n_mode'] ?? '') === 'exclude';
80  // Return if BE-user has no access rights to this field, @todo: another user access rights check!
81  if (($fieldIsExcluded && !$fieldNotExcludable) || ($isOverlay && $fieldExcludedFromTranslatedRecords) || $this->‪inlineFieldShouldBeSkipped()) {
82  return $resultArray;
83  }
84 
85  $tsConfig = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'] ?? [];
86  $parameterArray['fieldTSConfig'] = is_array($tsConfig) ? $tsConfig : [];
87 
88  if ($parameterArray['fieldTSConfig']['disabled'] ?? false) {
89  return $resultArray;
90  }
91 
92  // Override fieldConf by fieldTSconfig:
93  $parameterArray['fieldConf']['config'] = ‪FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
94  $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
95  $newElementBaseName = isset($this->data['elementBaseName']) ? $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']' : '';
96 
97  // The value to show in the form field.
98  $parameterArray['itemFormElValue'] = $row[$fieldName];
99  // Set field to read-only if configured for translated records to show default language content as readonly
100  // Note: In such case, the database value of this field was already overridden by DatabaseRowDefaultAsReadonly.
101  if (($parameterArray['fieldConf']['l10n_display'] ?? false)
102  && ‪GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly')
103  && $isOverlay
104  ) {
105  $parameterArray['fieldConf']['config']['readOnly'] = true;
106  }
107 
108  $processedTcaType = $this->data['processedTca']['ctrl']['type'] ?? '';
109  $typeField = !str_contains($processedTcaType, ':')
110  ? $processedTcaType
111  : substr($processedTcaType, 0, (int)strpos($processedTcaType, ':'));
112 
113  // JavaScript code for event handlers:
114  $parameterArray['fieldChangeFunc'] = [];
115  $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = new ‪UpdateValueOnFieldChange(
116  $table,
117  (string)$row['uid'],
118  $fieldName,
119  $parameterArray['itemFormElName']
120  );
121 
122  // Based on the type of the item, call a render function on a child element
123  $options = ‪$this->data;
124  $options['parameterArray'] = $parameterArray;
125  $options['elementBaseName'] = $newElementBaseName;
126  if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
127  $options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
128  } else {
129  // Fallback to type if no renderType is given
130  $options['renderType'] = $parameterArray['fieldConf']['config']['type'];
131  }
132  $resultArray = $this->nodeFactory->create($options)->render();
133  if ($resultArray['html'] !== '') {
134  // Render a custom HTML element which will ask the user to save/update the form due to changing the element.
135  // This is used for e.g. "type" fields and others configured with "onChange"
136  // (https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Columns/Properties/OnChange.html)
137  $requestFormEngineUpdate =
138  (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField)
139  || (isset($parameterArray['fieldConf']['onChange']) && $parameterArray['fieldConf']['onChange'] === 'reload');
140  if ($requestFormEngineUpdate) {
141  $askForUpdate = $backendUser->jsConfirmation(‪JsConfirmation::TYPE_CHANGE);
142  $requestMode = $askForUpdate ? 'ask' : 'enforce';
143  $fieldSelector = sprintf('[name="%s"]', $parameterArray['itemFormElName']);
144  $resultArray['html'] .= '<typo3-formengine-updater mode="' . htmlspecialchars($requestMode) . '" field="' . htmlspecialchars($fieldSelector) . '"></typo3-formengine-updater>';
145  }
146  }
147  return $resultArray;
148  }
149 
155  protected function ‪inlineFieldShouldBeSkipped()
156  {
157  $table = $this->data['tableName'];
158  $fieldName = $this->data['fieldName'];
159  $fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config'];
160 
161  $fieldConfig += [
162  'MM' => '',
163  'foreign_table' => '',
164  'foreign_selector' => '',
165  'foreign_field' => '',
166  ];
167 
168  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
169  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
170  $structureDepth = $inlineStackProcessor->getStructureDepth();
171 
172  $skipThisField = false;
173  if ($structureDepth > 0) {
174  $searchArray = [
175  '%OR' => [
176  'config' => [
177  0 => [
178  '%AND' => [
179  'foreign_table' => $table,
180  '%OR' => [
181  '%AND' => [
182  'appearance' => ['useCombination' => true],
183  'foreign_selector' => $fieldName,
184  ],
185  'MM' => $fieldConfig['MM'],
186  ],
187  ],
188  ],
189  1 => [
190  '%AND' => [
191  'foreign_table' => $fieldConfig['foreign_table'],
192  'foreign_selector' => $fieldConfig['foreign_field'],
193  ],
194  ],
195  ],
196  ],
197  ];
198  // Get the parent record from structure stack
199  $level = $inlineStackProcessor->getStructureLevel(-1) ?: [];
200  // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
201  if ($this->data['isOnSymmetricSide']) {
202  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
203  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
204  } else {
205  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
206  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
207  }
208  $skipThisField = $this->‪arrayCompareComplex($level, $searchArray);
209  }
210  return $skipThisField;
211  }
212 
245  protected function ‪arrayCompareComplex($subjectArray, $searchArray, $type = '')
246  {
247  $localMatches = 0;
248  $localEntries = 0;
249  if (is_array($searchArray) && !empty($searchArray)) {
250  // If no type was passed, try to determine
251  if (!$type) {
252  reset($searchArray);
253  $type = (string)key($searchArray);
254  $searchArray = current($searchArray);
255  }
256  // We use '%AND' and '%OR' in uppercase
257  $type = strtoupper($type);
258  // Split regular elements from sub elements
259  foreach ($searchArray as $key => $value) {
260  $localEntries++;
261  // Process a sub-group of OR-conditions
262  if ($key === '%OR') {
263  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
264  } elseif ($key === '%AND') {
265  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
266  } elseif (is_array($value) && $this->‪isAssociativeArray($searchArray)) {
267  $localMatches += $this->‪arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
268  } elseif (is_array($value)) {
269  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
270  } else {
271  if (isset($subjectArray[$key]) && isset($value)) {
272  // Boolean match:
273  if (is_bool($value)) {
274  $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
275  } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
276  $localMatches += $subjectArray[$key] == $value ? 1 : 0;
277  } else {
278  $localMatches += $subjectArray[$key] === $value ? 1 : 0;
279  }
280  }
281  }
282  // If one or more matches are required ('OR'), return TRUE after the first successful match
283  if ($type === '%OR' && $localMatches > 0) {
284  return true;
285  }
286  // If all matches are required ('AND') and we have no result after the first run, return FALSE
287  if ($type === '%AND' && $localMatches == 0) {
288  return false;
289  }
290  }
291  }
292  // Return the result for '%AND' (if nothing was checked, TRUE is returned)
293  return $localEntries === $localMatches;
294  }
295 
302  protected function ‪isAssociativeArray($object)
303  {
304  return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1);
305  }
306 
308  {
309  return ‪$GLOBALS['LANG'];
310  }
311 }
‪TYPO3\CMS\Backend\Form\Behavior\UpdateValueOnFieldChange
Definition: UpdateValueOnFieldChange.php:25
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\render
‪array render()
Definition: SingleFieldContainer.php:42
‪TYPO3\CMS\Backend\Form\Container
Definition: AbstractContainer.php:16
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\getLanguageService
‪getLanguageService()
Definition: SingleFieldContainer.php:307
‪TYPO3\CMS\Core\Authentication\JsConfirmation
Definition: JsConfirmation.php:28
‪TYPO3\CMS\Backend\Form\Utility\FormEngineUtility
Definition: FormEngineUtility.php:41
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer
Definition: SingleFieldContainer.php:35
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\isAssociativeArray
‪bool isAssociativeArray($object)
Definition: SingleFieldContainer.php:302
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer\getBackendUserAuthentication
‪getBackendUserAuthentication()
Definition: AbstractContainer.php:149
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:35
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer
Definition: AbstractContainer.php:29
‪TYPO3\CMS\Core\Authentication\JsConfirmation\TYPE_CHANGE
‪const TYPE_CHANGE
Definition: JsConfirmation.php:29
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\inlineFieldShouldBeSkipped
‪bool inlineFieldShouldBeSkipped()
Definition: SingleFieldContainer.php:155
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:422
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:32
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\arrayCompareComplex
‪bool arrayCompareComplex($subjectArray, $searchArray, $type='')
Definition: SingleFieldContainer.php:245
‪TYPO3\CMS\Backend\Form\Utility\FormEngineUtility\overrideFieldConf
‪static array overrideFieldConf($fieldConfig, $TSconfig)
Definition: FormEngineUtility.php:79
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪initializeResultArray()
Definition: AbstractNode.php:77