‪TYPO3CMS  10.4
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()
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']])
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  // Check if this field is configured and editable according to exclude fields and other configuration
78  if (// Return if BE-user has no access rights to this field, @todo: another user access rights check!
79  $parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
80  // Return if field should not be rendered in translated records
81  || $isOverlay && empty($parameterArray['fieldConf']['l10n_display']) && $parameterArray['fieldConf']['l10n_mode'] === 'exclude'
83  ) {
84  return $resultArray;
85  }
86 
87  $parameterArray['fieldTSConfig'] = [];
88  if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])
89  && is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])
90  ) {
91  $parameterArray['fieldTSConfig'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'];
92  }
93  if ($parameterArray['fieldTSConfig']['disabled']) {
94  return $resultArray;
95  }
96 
97  // Override fieldConf by fieldTSconfig:
98  $parameterArray['fieldConf']['config'] = ‪FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
99  $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
100  $parameterArray['itemFormElID'] = 'data_' . $table . '_' . $row['uid'] . '_' . $fieldName;
101  $newElementBaseName = $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
102 
103  // The value to show in the form field.
104  $parameterArray['itemFormElValue'] = $row[$fieldName];
105  // Set field to read-only if configured for translated records to show default language content as readonly
106  if ($parameterArray['fieldConf']['l10n_display']
107  && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly')
108  && $isOverlay
109  ) {
110  $parameterArray['fieldConf']['config']['readOnly'] = true;
111  $parameterArray['itemFormElValue'] = $this->data['defaultLanguageRow'][$fieldName];
112  }
113 
114  if (strpos($this->data['processedTca']['ctrl']['type'], ':') === false) {
115  $typeField = $this->data['processedTca']['ctrl']['type'];
116  } else {
117  $typeField = substr($this->data['processedTca']['ctrl']['type'], 0, strpos($this->data['processedTca']['ctrl']['type'], ':'));
118  }
119  // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
120  // This is used for eg. "type" fields and others configured with "onChange"
121  if (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField
122  || isset($parameterArray['fieldConf']['onChange']) && $parameterArray['fieldConf']['onChange'] === 'reload'
123  ) {
124  if ($backendUser->jsConfirmation(‪JsConfirmation::TYPE_CHANGE)) {
125  $alertMsgOnChange = 'Modal.confirm('
126  . 'TYPO3.lang["FormEngine.refreshRequiredTitle"],'
127  . ' TYPO3.lang["FormEngine.refreshRequiredContent"]'
128  . ')'
129  . '.on('
130  . '"button.clicked",'
131  . ' function(e) { if (e.target.name == "ok") { FormEngine.saveDocument(); } Modal.dismiss(); }'
132  . ');';
133  } else {
134  $alertMsgOnChange = 'FormEngine.saveDocument();';
135  }
136  } else {
137  $alertMsgOnChange = '';
138  }
139 
140  // JavaScript code for event handlers:
141  $parameterArray['fieldChangeFunc'] = [];
142  $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged('
143  . GeneralUtility::quoteJSvalue($table) . ','
144  . GeneralUtility::quoteJSvalue($row['uid']) . ','
145  . GeneralUtility::quoteJSvalue($fieldName) . ','
146  . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'])
147  . ');';
148  if ($alertMsgOnChange) {
149  $parameterArray['fieldChangeFunc']['alert'] = 'require([\'TYPO3/CMS/Backend/FormEngine\', \'TYPO3/CMS/Backend/Modal\'], function (FormEngine, Modal) {' . $alertMsgOnChange . '});';
150  }
151 
152  // Based on the type of the item, call a render function on a child element
153  $options = ‪$this->data;
154  $options['parameterArray'] = $parameterArray;
155  $options['elementBaseName'] = $newElementBaseName;
156  if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
157  $options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
158  } else {
159  // Fallback to type if no renderType is given
160  $options['renderType'] = $parameterArray['fieldConf']['config']['type'];
161  }
162  $resultArray = $this->nodeFactory->create($options)->render();
163  return $resultArray;
164  }
165 
175  protected function ‪isInlineChildAndLabelField($table, $field)
176  {
178  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
179  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
180  $level = $inlineStackProcessor->getStructureLevel(-1);
181  if ($level['config']['foreign_label']) {
182  $label = $level['config']['foreign_label'];
183  } else {
184  $label = $this->data['processedTca']['ctrl']['label'];
185  }
186  return $level['config']['foreign_table'] === $table && $label === $field;
187  }
188 
194  protected function ‪inlineFieldShouldBeSkipped()
195  {
196  $table = $this->data['tableName'];
197  $fieldName = $this->data['fieldName'];
198  $fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config'];
199 
201  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
202  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
203  $structureDepth = $inlineStackProcessor->getStructureDepth();
204 
205  $skipThisField = false;
206  if ($structureDepth > 0) {
207  $searchArray = [
208  '%OR' => [
209  'config' => [
210  0 => [
211  '%AND' => [
212  'foreign_table' => $table,
213  '%OR' => [
214  '%AND' => [
215  'appearance' => ['useCombination' => true],
216  'foreign_selector' => $fieldName
217  ],
218  'MM' => $fieldConfig['MM']
219  ]
220  ]
221  ],
222  1 => [
223  '%AND' => [
224  'foreign_table' => $fieldConfig['foreign_table'],
225  'foreign_selector' => $fieldConfig['foreign_field']
226  ]
227  ]
228  ]
229  ]
230  ];
231  // Get the parent record from structure stack
232  $level = $inlineStackProcessor->getStructureLevel(-1);
233  // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
234  if ($this->data['isOnSymmetricSide']) {
235  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
236  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
237  } else {
238  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
239  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
240  }
241  $skipThisField = $this->‪arrayCompareComplex($level, $searchArray);
242  }
243  return $skipThisField;
244  }
245 
278  protected function ‪arrayCompareComplex($subjectArray, $searchArray, $type = '')
279  {
280  $localMatches = 0;
281  $localEntries = 0;
282  if (is_array($searchArray) && !empty($searchArray)) {
283  // If no type was passed, try to determine
284  if (!$type) {
285  reset($searchArray);
286  $type = (string)key($searchArray);
287  $searchArray = current($searchArray);
288  }
289  // We use '%AND' and '%OR' in uppercase
290  $type = strtoupper($type);
291  // Split regular elements from sub elements
292  foreach ($searchArray as $key => $value) {
293  $localEntries++;
294  // Process a sub-group of OR-conditions
295  if ($key === '%OR') {
296  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
297  } elseif ($key === '%AND') {
298  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
299  } elseif (is_array($value) && $this->‪isAssociativeArray($searchArray)) {
300  $localMatches += $this->‪arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
301  } elseif (is_array($value)) {
302  $localMatches += $this->‪arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
303  } else {
304  if (isset($subjectArray[$key]) && isset($value)) {
305  // Boolean match:
306  if (is_bool($value)) {
307  $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
308  } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
309  $localMatches += $subjectArray[$key] == $value ? 1 : 0;
310  } else {
311  $localMatches += $subjectArray[$key] === $value ? 1 : 0;
312  }
313  }
314  }
315  // If one or more matches are required ('OR'), return TRUE after the first successful match
316  if ($type === '%OR' && $localMatches > 0) {
317  return true;
318  }
319  // If all matches are required ('AND') and we have no result after the first run, return FALSE
320  if ($type === '%AND' && $localMatches == 0) {
321  return false;
322  }
323  }
324  }
325  // Return the result for '%AND' (if nothing was checked, TRUE is returned)
326  return $localEntries === $localMatches;
327  }
328 
335  protected function ‪isAssociativeArray($object)
336  {
337  return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1);
338  }
339 
343  protected function ‪getBackendUserAuthentication()
344  {
345  return ‪$GLOBALS['BE_USER'];
346  }
347 
351  protected function ‪getLanguageService()
352  {
353  return ‪$GLOBALS['LANG'];
354  }
355 }
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\getBackendUserAuthentication
‪BackendUserAuthentication getBackendUserAuthentication()
Definition: SingleFieldContainer.php:343
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:90
‪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\Utility\FormEngineUtility
Definition: FormEngineUtility.php:39
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer
Definition: SingleFieldContainer.php:35
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\isAssociativeArray
‪bool isAssociativeArray($object)
Definition: SingleFieldContainer.php:335
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:42
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer
Definition: AbstractContainer.php:28
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\isInlineChildAndLabelField
‪bool isInlineChildAndLabelField($table, $field)
Definition: SingleFieldContainer.php:175
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\inlineFieldShouldBeSkipped
‪bool inlineFieldShouldBeSkipped()
Definition: SingleFieldContainer.php:194
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\getLanguageService
‪LanguageService getLanguageService()
Definition: SingleFieldContainer.php:351
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\Type\Bitmask\JsConfirmation\TYPE_CHANGE
‪const TYPE_CHANGE
Definition: JsConfirmation.php:29
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Type\Bitmask\JsConfirmation
Definition: JsConfirmation.php:25
‪TYPO3\CMS\Backend\Form\Container\SingleFieldContainer\arrayCompareComplex
‪bool arrayCompareComplex($subjectArray, $searchArray, $type='')
Definition: SingleFieldContainer.php:278
‪TYPO3\CMS\Backend\Form\Utility\FormEngineUtility\overrideFieldConf
‪static array overrideFieldConf($fieldConfig, $TSconfig)
Definition: FormEngineUtility.php:66