$fieldConfig) { if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') { continue; } // Make sure we are only processing supported renderTypes if (!$this->isTargetRenderType($fieldConfig)) { continue; } $fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange($fieldConfig['config']['maxitems'] ?? 0, 0, 99999); if ($fieldConfig['config']['maxitems'] === 0) { $fieldConfig['config']['maxitems'] = 99999; } $fieldConfig = $this->parseStartingPointsFromSiteConfiguration($result, $fieldConfig); // A couple of tree specific config parameters can be overwritten via page TS. // Pick those that influence the data fetching and write them into the config // given to the tree data provider. This is additionally used in SelectTreeElement, so always do that. if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['config.']['treeConfig.'])) { $pageTsConfig = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['config.']['treeConfig.']; if (isset($pageTsConfig['startingPoints'])) { $fieldConfig['config']['treeConfig']['startingPoints'] = implode(',', array_unique(GeneralUtility::intExplode(',', (string)$pageTsConfig['startingPoints']))); } if (isset($pageTsConfig['appearance.']['expandAll'])) { $fieldConfig['config']['treeConfig']['appearance']['expandAll'] = (bool)$pageTsConfig['appearance.']['expandAll']; } if (isset($pageTsConfig['appearance.']['maxLevels'])) { $fieldConfig['config']['treeConfig']['appearance']['maxLevels'] = (int)$pageTsConfig['appearance.']['maxLevels']; } if (isset($pageTsConfig['appearance.']['nonSelectableLevels'])) { $fieldConfig['config']['treeConfig']['appearance']['nonSelectableLevels'] = $pageTsConfig['appearance.']['nonSelectableLevels']; } } // Prepare the list of currently selected nodes using RelationHandler // This is needed to ensure a correct value initialization before the actual tree is loaded $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, []); // Preserve original TCA static items before overwriting with resolved items $originalTcaItems = $fieldConfig['config']['items'] ?? []; // Always resolve the full item list (static + foreign_table + TSconfig) with filtering. // This is needed by TcaColumnsRemoveEmptyRelations to determine if the field has any // selectable items, and is reused below for tree building in the AJAX context. $staticItems = $this->sanitizeItemArray($originalTcaItems, $table, $fieldName); $staticItems = array_merge($staticItems, $this->addItemsFromPageTsConfig($result, $fieldName, [])); $staticItems = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $staticItems); $staticItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $staticItems); $dynamicItems = $this->addItemsFromForeignTable($result, $fieldName); $dynamicItems = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $dynamicItems); $dynamicItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $dynamicItems); $dynamicItems = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $dynamicItems); $dynamicItems = $this->removeItemsByUserAuthMode($result, $fieldName, $dynamicItems); $dynamicItems = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $dynamicItems); // Store flat items for downstream providers (will be overwritten with tree structure during AJAX) $fieldConfig['config']['items'] = array_merge($staticItems, $dynamicItems); if ($result['selectTreeCompileItems']) { $finalItems = []; // Prepare the list of "static" items if there are any. // "static" and "dynamic" is separated since the tree code only copes with "real" existing foreign nodes, // so this "static" stuff allows defining tree items that don't really exist in the tree. $itemsFromTca = $this->sanitizeItemArray($originalTcaItems, $table, $fieldName); // List of additional items defined by page ts config "addItems" $itemsFromPageTsConfig = $this->addItemsFromPageTsConfig($result, $fieldName, []); // Resolve pageTsConfig item icons to markup $finalPageTsConfigItems = []; foreach ($itemsFromPageTsConfig as $item) { if ($item['icon'] !== null) { $item['icon'] = $this->iconFactory->getIcon($item['icon'], IconSize::SMALL)->getMarkup('inline'); } $finalPageTsConfigItems[] = $item; } if (!empty($itemsFromTca) || !empty($finalPageTsConfigItems)) { // First apply "keepItems" to $itemsFromTca, this will restrict the tca item list to only // those items that are defined in page ts "keepItems" if given $itemsFromTca = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $itemsFromTca); // Then, merge the items from page ts "addItems" into item list, since "addItems" should // add additional items even if they are not in the "keepItems" list $staticItems = array_merge($itemsFromTca, $finalPageTsConfigItems); // Now apply page ts config "removeItems", so this is *after* addItems, so "removeItems" could // possibly remove items again that were added via "addItems" $staticItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $staticItems); // Now, apply user and access right restrictions to this item list $staticItems = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $staticItems); $staticItems = $this->removeItemsByUserAuthMode($result, $fieldName, $staticItems); $staticItems = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $staticItems); // Call itemsProcFunc if given. Note this function does *not* see the "dynamic" list of items if (!empty($fieldConfig['config']['itemsProcFunc']) || !empty($fieldConfig['config']['itemsProcessors'])) { $staticItems = $this->resolveItemsProcessorFunction($result, $fieldName, $staticItems); // itemsProcFunc must not be used anymore unset( $fieldConfig['config']['itemsProcFunc'], $fieldConfig['config']['itemsProcessors'] ); } // translate any labels $staticItems = $this->translateLabels($result, $staticItems, $table, $fieldName); // and add icons from the static list $staticItems = $this->addIconFromAltIcons($result, $staticItems, $table, $fieldName); // Now compile the target items using the same array structure as the "dynamic" list below foreach ($staticItems as $item) { if ($item['value'] === '--div--') { // Skip divs that may occur here for whatever reason continue; } $finalItems[] = [ 'identifier' => $item['value'], 'name' => $item['label'], 'icon' => $item['icon'] ?? '', 'iconOverlay' => '', 'depth' => 0, 'hasChildren' => false, 'selectable' => true, 'checked' => in_array($item['value'], $result['databaseRow'][$fieldName]), ]; } } // Reuse the already-fetched dynamic items to build uid whitelist for tree $uidListOfAllDynamicItems = []; foreach ($dynamicItems as $item) { if ((int)$item['value'] > 0) { $uidListOfAllDynamicItems[] = (int)$item['value']; } } // Now kick in this tree stuff $treeDataProvider = TreeDataProviderFactory::getDataProvider( $fieldConfig['config'], $table, $fieldName, $result['databaseRow'] ); $treeDataProvider->setSelectedList(implode(',', $result['databaseRow'][$fieldName])); // Basically the tree foo fetches all tree nodes again (aaargs), then verifies if // a given rows uid is within this "list of allowed uids". It then creates an object // tree representing the nested tree, just to collapse all that to a flat array again. Yay ... $treeDataProvider->setItemWhiteList($uidListOfAllDynamicItems); $treeDataProvider->initializeTreeData(); $treeRenderer = GeneralUtility::makeInstance(ArrayTreeRenderer::class); $tree = GeneralUtility::makeInstance(TableConfigurationTree::class); $tree->setDataProvider($treeDataProvider); $tree->setNodeRenderer($treeRenderer); // Merge tree nodes after calculated nodes from static items $fieldConfig['config']['items'] = array_merge($finalItems, $tree->render()); } $result['processedTca']['columns'][$fieldName] = $fieldConfig; } return $result; } /** * Determines whether the current field is a valid target for this DataProvider * * @return bool */ protected function isTargetRenderType(array $fieldConfig) { return ($fieldConfig['config']['renderType'] ?? '') === 'selectTree'; } }