TYPO3 CMS  TYPO3_8-7
SingleFieldContainer.php
Go to the documentation of this file.
1 <?php
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 
24 
34 {
41  public function render()
42  {
43  $backendUser = $this->getBackendUserAuthentication();
44  $resultArray = $this->initializeResultArray();
45 
46  $table = $this->data['tableName'];
47  $row = $this->data['databaseRow'];
48  $fieldName = $this->data['fieldName'];
49 
50  $parameterArray = [];
51  $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName];
52 
53  $isOverlay = false;
54 
55  // This field decides whether the current record is an overlay (as opposed to being a standalone record)
56  // Based on this decision we need to trigger field exclusion or special rendering (like readOnly)
57  if (isset($this->data['processedTca']['ctrl']['transOrigPointerField'])
58  && is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']])
59  ) {
60  $parentValue = $row[$this->data['processedTca']['ctrl']['transOrigPointerField']];
61  if (MathUtility::canBeInterpretedAsInteger($parentValue)) {
62  $isOverlay = (bool)$parentValue;
63  } elseif (is_array($parentValue)) {
64  // This case may apply if the value has been converted to an array by the select or group data provider
65  $isOverlay = !empty($parentValue) ? (bool)$parentValue[0] : false;
66  } else {
67  throw new \InvalidArgumentException(
68  'The given value "' . $parentValue . '" for the original language field ' . $this->data['processedTca']['ctrl']['transOrigPointerField']
69  . ' of table ' . $table . ' is invalid.',
70  1470742770
71  );
72  }
73  }
74 
75  // A couple of early returns in case the field should not be rendered
76  // Check if this field is configured and editable according to exclude fields and other configuration
77  if (// Return if BE-user has no access rights to this field, @todo: another user access rights check!
78  $parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
79  || $parameterArray['fieldConf']['config']['type'] === 'passthrough'
80  // Return if field should not be rendered in translated records
81  || $isOverlay && empty($parameterArray['fieldConf']['l10n_display']) && $parameterArray['fieldConf']['l10n_mode'] === 'exclude'
82  || $this->inlineFieldShouldBeSkipped()
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 = 'top.TYPO3.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" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); }'
132  . ');';
133  } else {
134  $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
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'] = $alertMsgOnChange;
150  }
151 
152  // If this is the child of an inline type and it is the field creating the label
153  if ($this->isInlineChildAndLabelField($table, $fieldName)) {
154  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
155  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
156  $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
157  $inlineObjectId = implode(
158  '-',
159  [
160  $inlineDomObjectId,
161  $table,
162  $row['uid']
163  ]
164  );
165  $parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField('
166  . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ','
167  . GeneralUtility::quoteJSvalue($inlineObjectId)
168  . ');';
169  }
170 
171  // Based on the type of the item, call a render function on a child element
172  $options = $this->data;
173  $options['parameterArray'] = $parameterArray;
174  $options['elementBaseName'] = $newElementBaseName;
175  if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
176  $options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
177  } else {
178  // Fallback to type if no renderType is given
179  $options['renderType'] = $parameterArray['fieldConf']['config']['type'];
180  }
181  $resultArray = $this->nodeFactory->create($options)->render();
182  return $resultArray;
183  }
184 
194  protected function isInlineChildAndLabelField($table, $field)
195  {
197  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
198  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
199  $level = $inlineStackProcessor->getStructureLevel(-1);
200  if ($level['config']['foreign_label']) {
201  $label = $level['config']['foreign_label'];
202  } else {
203  $label = $this->data['processedTca']['ctrl']['label'];
204  }
205  return $level['config']['foreign_table'] === $table && $label === $field;
206  }
207 
213  protected function inlineFieldShouldBeSkipped()
214  {
215  $table = $this->data['tableName'];
216  $fieldName = $this->data['fieldName'];
217  $fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config'];
218 
220  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
221  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
222  $structureDepth = $inlineStackProcessor->getStructureDepth();
223 
224  $skipThisField = false;
225  if ($structureDepth > 0) {
226  $searchArray = [
227  '%OR' => [
228  'config' => [
229  0 => [
230  '%AND' => [
231  'foreign_table' => $table,
232  '%OR' => [
233  '%AND' => [
234  'appearance' => ['useCombination' => true],
235  'foreign_selector' => $fieldName
236  ],
237  'MM' => $fieldConfig['MM']
238  ]
239  ]
240  ],
241  1 => [
242  '%AND' => [
243  'foreign_table' => $fieldConfig['foreign_table'],
244  'foreign_selector' => $fieldConfig['foreign_field']
245  ]
246  ]
247  ]
248  ]
249  ];
250  // Get the parent record from structure stack
251  $level = $inlineStackProcessor->getStructureLevel(-1);
252  // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
253  if ($this->data['isOnSymmetricSide']) {
254  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
255  $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
256  } else {
257  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
258  $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
259  }
260  $skipThisField = $this->arrayCompareComplex($level, $searchArray);
261  }
262  return $skipThisField;
263  }
264 
297  protected function arrayCompareComplex($subjectArray, $searchArray, $type = '')
298  {
299  $localMatches = 0;
300  $localEntries = 0;
301  if (is_array($searchArray) && !empty($searchArray)) {
302  // If no type was passed, try to determine
303  if (!$type) {
304  reset($searchArray);
305  $type = key($searchArray);
306  $searchArray = current($searchArray);
307  }
308  // We use '%AND' and '%OR' in uppercase
309  $type = strtoupper($type);
310  // Split regular elements from sub elements
311  foreach ($searchArray as $key => $value) {
312  $localEntries++;
313  // Process a sub-group of OR-conditions
314  if ($key === '%OR') {
315  $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
316  } elseif ($key === '%AND') {
317  $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
318  } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) {
319  $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
320  } elseif (is_array($value)) {
321  $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
322  } else {
323  if (isset($subjectArray[$key]) && isset($value)) {
324  // Boolean match:
325  if (is_bool($value)) {
326  $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
327  } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
328  $localMatches += $subjectArray[$key] == $value ? 1 : 0;
329  } else {
330  $localMatches += $subjectArray[$key] === $value ? 1 : 0;
331  }
332  }
333  }
334  // If one or more matches are required ('OR'), return TRUE after the first successful match
335  if ($type === '%OR' && $localMatches > 0) {
336  return true;
337  }
338  // If all matches are required ('AND') and we have no result after the first run, return FALSE
339  if ($type === '%AND' && $localMatches == 0) {
340  return false;
341  }
342  }
343  }
344  // Return the result for '%AND' (if nothing was checked, TRUE is returned)
345  return $localEntries === $localMatches;
346  }
347 
354  protected function isAssociativeArray($object)
355  {
356  return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1);
357  }
358 
362  protected function getBackendUserAuthentication()
363  {
364  return $GLOBALS['BE_USER'];
365  }
366 
370  protected function getLanguageService()
371  {
372  return $GLOBALS['LANG'];
373  }
374 }
static makeInstance($className,... $constructorArguments)
static static overrideFieldConf($fieldConfig, $TSconfig)
arrayCompareComplex($subjectArray, $searchArray, $type='')
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']