getBackendUserAuthentication(); $resultArray = $this->initializeResultArray(); $table = $this->data['tableName']; $row = $this->data['databaseRow']; $fieldName = $this->data['fieldName']; $parameterArray = []; $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName]; $isOverlay = false; // This field decides whether the current record is an overlay (as opposed to being a standalone record) // Based on this decision we need to trigger field exclusion or special rendering (like readOnly) if (isset($this->data['processedTca']['ctrl']['transOrigPointerField']) && is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']] ?? null) ) { $parentValue = $row[$this->data['processedTca']['ctrl']['transOrigPointerField']]; if (MathUtility::canBeInterpretedAsInteger($parentValue)) { $isOverlay = (bool)$parentValue; } elseif (is_array($parentValue)) { // This case may apply if the value has been converted to an array by the select or group data provider $isOverlay = !empty($parentValue) ? (bool)$parentValue[0] : false; } else { throw new \InvalidArgumentException( 'The given value "' . $parentValue . '" for the original language field ' . $this->data['processedTca']['ctrl']['transOrigPointerField'] . ' of table ' . $table . ' is invalid.', 1470742770 ); } } // A couple of early returns in case the field should not be rendered $fieldIsExcluded = $parameterArray['fieldConf']['exclude'] ?? false; $fieldNotExcludable = $backendUser->check('non_exclude_fields', $table . ':' . $fieldName); $fieldExcludedFromTranslatedRecords = empty($parameterArray['fieldConf']['l10n_display']) && ($parameterArray['fieldConf']['l10n_mode'] ?? '') === 'exclude'; // Return if BE-user has no access rights to this field, @todo: another user access rights check! if (($fieldIsExcluded && !$fieldNotExcludable) || ($isOverlay && $fieldExcludedFromTranslatedRecords) || $this->inlineFieldShouldBeSkipped()) { return $resultArray; } $tsConfig = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'] ?? []; $parameterArray['fieldTSConfig'] = is_array($tsConfig) ? $tsConfig : []; if ($parameterArray['fieldTSConfig']['disabled'] ?? false) { return $resultArray; } // Override fieldConf by fieldTSconfig: $parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']); $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'; $newElementBaseName = isset($this->data['elementBaseName']) ? $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']' : ''; // The value to show in the form field. $parameterArray['itemFormElValue'] = $row[$fieldName]; // Set field to read-only if configured for translated records to show default language content as readonly // Note: In such case, the database value of this field was already overridden by DatabaseRowDefaultAsReadonly. if (($parameterArray['fieldConf']['l10n_display'] ?? false) && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly') && $isOverlay ) { $parameterArray['fieldConf']['config']['readOnly'] = true; } $processedTcaType = $this->data['processedTca']['ctrl']['type'] ?? ''; $typeField = !str_contains($processedTcaType, ':') ? $processedTcaType : substr($processedTcaType, 0, (int)strpos($processedTcaType, ':')); // JavaScript code for event handlers: $parameterArray['fieldChangeFunc'] = []; $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = new UpdateValueOnFieldChange( $table, (string)$row['uid'], $fieldName, $parameterArray['itemFormElName'] ); $requestFormEngineUpdate = (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField) || (isset($parameterArray['fieldConf']['onChange']) && $parameterArray['fieldConf']['onChange'] === 'reload'); if ($requestFormEngineUpdate) { $askForUpdate = $backendUser->jsConfirmation(JsConfirmation::TYPE_CHANGE); $parameterArray['fieldChangeFunc']['record_type_changed'] = new ReloadOnFieldChange($askForUpdate); } // Based on the type of the item, call a render function on a child element $options = $this->data; $options['parameterArray'] = $parameterArray; $options['elementBaseName'] = $newElementBaseName; if (!empty($parameterArray['fieldConf']['config']['renderType'])) { $options['renderType'] = $parameterArray['fieldConf']['config']['renderType']; } else { // Fallback to type if no renderType is given $options['renderType'] = $parameterArray['fieldConf']['config']['type']; } return $this->nodeFactory->create($options)->render(); } /** * Rendering of inline fields should be skipped under certain circumstances */ protected function inlineFieldShouldBeSkipped(): bool { $table = $this->data['tableName']; $fieldName = $this->data['fieldName']; $fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config']; $fieldConfig += [ 'MM' => '', 'foreign_table' => '', 'foreign_selector' => '', 'foreign_field' => '', ]; if (($this->data['inlineStructure']['stable'] ?? []) !== []) { $searchArray = [ '%OR' => [ 'config' => [ 0 => [ '%AND' => [ 'foreign_table' => $table, '%OR' => [ '%AND' => [ 'appearance' => ['useCombination' => true], 'foreign_selector' => $fieldName, ], 'MM' => $fieldConfig['MM'], ], ], ], 1 => [ '%AND' => [ 'foreign_table' => $fieldConfig['foreign_table'], 'foreign_selector' => $fieldConfig['foreign_field'], ], ], ], ], ]; // If we have symmetric fields, check on which side we are and hide fields, that are set automatically: if ($this->data['isOnSymmetricSide']) { $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName; $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName; } else { $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName; $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName; } // Parent record from structure stack $parent = $this->inlineStackProcessor->getStructureLevelFromStructure($this->data['inlineStructure'], -1) ?? []; return $this->arrayCompareComplex($parent, $searchArray); } return false; } /** * Handles complex comparison requests on an array. * A request could look like the following: * * $searchArray = array( * '%AND' => array( * 'key1' => 'value1', * 'key2' => 'value2', * '%OR' => array( * 'subarray' => array( * 'subkey' => 'subvalue' * ), * 'key3' => 'value3', * 'key4' => 'value4' * ) * ) * ); * * It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent * overwriting the sub-array. It could be necessary, if you use complex comparisons. * * The example above means, key1 *AND* key2 (and their values) have to match with * the $subjectArray and additional one *OR* key3 or key4 have to meet the same * condition. * It is also possible to compare parts of a sub-array (e.g. "subarray"), so this * function recurses down one level in that sub-array. * * @param array $subjectArray The array to search in * @param array $searchArray The array with keys and values to search for * @param string $type Use '%AND' or '%OR' for comparison * @return bool The result of the comparison */ protected function arrayCompareComplex(array $subjectArray, array $searchArray, string $type = ''): bool { $localMatches = 0; $localEntries = 0; if ($searchArray !== []) { // If no type was passed, try to determine if (!$type) { reset($searchArray); $type = (string)key($searchArray); $searchArray = current($searchArray); } // We use '%AND' and '%OR' in uppercase $type = strtoupper($type); // Split regular elements from sub elements foreach ($searchArray as $key => $value) { $localEntries++; // Process a sub-group of OR-conditions if ($key === '%OR') { $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0; } elseif ($key === '%AND') { $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0; } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) { $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0; } elseif (is_array($value)) { $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0; } else { if (isset($subjectArray[$key]) && isset($value)) { // Boolean match: if (is_bool($value)) { $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0; } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) { $localMatches += $subjectArray[$key] == $value ? 1 : 0; } else { $localMatches += $subjectArray[$key] === $value ? 1 : 0; } } } // If one or more matches are required ('OR'), return TRUE after the first successful match if ($type === '%OR' && $localMatches > 0) { return true; } // If all matches are required ('AND') and we have no result after the first run, return FALSE if ($type === '%AND' && $localMatches == 0) { return false; } } } // Return the result for '%AND' (if nothing was checked, TRUE is returned) return $localEntries === $localMatches; } /** * Checks whether an object is an associative array. * * @param mixed $object The object to be checked * @return bool Returns TRUE, if the object is an associative array */ protected function isAssociativeArray($object) { return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1); } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }