‪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 
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 eg. "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  return $resultArray;
147  }
148 
154  protected function ‪inlineFieldShouldBeSkipped()
155  {
156  $table = $this->data['tableName'];
157  $fieldName = $this->data['fieldName'];
158  $fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config'];
159 
160  $fieldConfig += [
161  'MM' => '',
162  'foreign_table' => '',
163  'foreign_selector' => '',
164  'foreign_field' => '',
165  ];
166 
167  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
168  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
169  $structureDepth = $inlineStackProcessor->getStructureDepth();
170 
171  $skipThisField = false;
172  if ($structureDepth > 0) {
173  $searchArray = [
174  '%OR' => [
175  'config' => [
176  0 => [
177  '%AND' => [
178  'foreign_table' => $table,
179  '%OR' => [
180  '%AND' => [
181  'appearance' => ['useCombination' => true],
182  'foreign_selector' => $fieldName,
183  ],
184  'MM' => $fieldConfig['MM'],
185  ],
186  ],
187  ],
188  1 => [
189  '%AND' => [
190  'foreign_table' => $fieldConfig['foreign_table'],
191  'foreign_selector' => $fieldConfig['foreign_field'],
192  ],
193  ],
194  ],
195  ],
196  ];
197  // Get the parent record from structure stack
198  $level = $inlineStackProcessor->getStructureLevel(-1) ?: [];
199  // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
200  if ($this->data['isOnSymmetricSide']) {
201  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
202  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
203  } else {
204  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
205  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
206  }
207  $skipThisField = $this->‪arrayCompareComplex($level, $searchArray);
208  }
209  return $skipThisField;
210  }
211 
244  protected function ‪arrayCompareComplex($subjectArray, $searchArray, $type = '')
245  {
246  $localMatches = 0;
247  $localEntries = 0;
248  if (is_array($searchArray) && !empty($searchArray)) {
249  // If no type was passed, try to determine
250  if (!$type) {
251  reset($searchArray);
252  $type = (string)key($searchArray);
253  $searchArray = current($searchArray);
254  }
255  // We use '%AND' and '%OR' in uppercase
256  $type = strtoupper($type);
257  // Split regular elements from sub elements
258  foreach ($searchArray as $key => $value) {
259  $localEntries++;
260  // Process a sub-group of OR-conditions
261  if ($key === '%OR') {
262  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
263  } elseif ($key === '%AND') {
264  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
265  } elseif (is_array($value) && $this->‪isAssociativeArray($searchArray)) {
266  $localMatches += $this->‪arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
267  } elseif (is_array($value)) {
268  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
269  } else {
270  if (isset($subjectArray[$key]) && isset($value)) {
271  // Boolean match:
272  if (is_bool($value)) {
273  $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
274  } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
275  $localMatches += $subjectArray[$key] == $value ? 1 : 0;
276  } else {
277  $localMatches += $subjectArray[$key] === $value ? 1 : 0;
278  }
279  }
280  }
281  // If one or more matches are required ('OR'), return TRUE after the first successful match
282  if ($type === '%OR' && $localMatches > 0) {
283  return true;
284  }
285  // If all matches are required ('AND') and we have no result after the first run, return FALSE
286  if ($type === '%AND' && $localMatches == 0) {
287  return false;
288  }
289  }
290  }
291  // Return the result for '%AND' (if nothing was checked, TRUE is returned)
292  return $localEntries === $localMatches;
293  }
294 
301  protected function ‪isAssociativeArray($object)
302  {
303  return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1);
304  }
305 
307  {
308  return ‪$GLOBALS['LANG'];
309  }
310 }
‪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:306
‪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:301
‪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:154
‪$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:244
‪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