transformArrayToSelectItems($allItems); $groupedItems = []; // Append defined groups at first, as their order is prioritized $itemGroups = ['none' => '']; foreach ($definedGroups as $groupId => $groupLabel) { $itemGroups[$groupId] = $this->getLanguageService()->sL($groupLabel); } $currentGroup = 'none'; // Extract --div-- into itemGroups foreach ($allItems as $item) { if ($item->isDivider()) { // A divider is added as a group (existing groups will get their label overridden) if ($item->hasGroup()) { $currentGroup = $item->getGroup(); $itemGroups[$currentGroup] = $item->getLabel(); } else { $currentGroup = 'none'; } continue; } // Put the given item in the currentGroup if no group has been given already if (!$item->hasGroup()) { $item = $item->withGroup($currentGroup); } $groupIdOfItem = $item->hasGroup() ? $item->getGroup() : 'none'; // It is still possible to have items that have an "unassigned" group, so they are moved to the "none" group if (!isset($itemGroups[$groupIdOfItem])) { $itemGroups[$groupIdOfItem] = ''; } // Put the item in its corresponding group (and create it if it does not exist yet) if (!is_array($groupedItems[$groupIdOfItem] ?? null)) { $groupedItems[$groupIdOfItem] = []; } $groupedItems[$groupIdOfItem][] = $item; } // Only "none" = no grouping used explicitly via "itemGroups" or via "--div--" if (count($itemGroups) === 1) { if (!empty($sortOrders)) { $allItems = $this->sortItems($allItems, $sortOrders); } return $this->transformSelectItemsToArray($allItems); } // $groupedItems contains all items per group // $itemGroups contains all groups in order of each group // Let's add the --div-- items again ("unpacking") // And use the group ordering given by the itemGroups $finalItems = []; foreach ($itemGroups as $groupId => $groupLabel) { $groupId = (string)$groupId; $itemsInGroup = $groupedItems[$groupId] ?? []; if (empty($itemsInGroup)) { continue; } // If sorting is defined, sort within each group now if (!empty($sortOrders)) { $itemsInGroup = $this->sortItems($itemsInGroup, $sortOrders); } // Add the --div-- if it is not the "none" default item if ($groupId !== 'none') { // Fall back to the groupId, if there is no label for it $groupLabel = $groupLabel ?: $groupId; $finalItems[] = ['label' => $groupLabel, 'value' => '--div--', 'group' => $groupId]; } $finalItems = array_merge($finalItems, $itemsInGroup); } return $this->transformSelectItemsToArray($finalItems); } /** * @return SelectItem[] */ public function transformArrayToSelectItems(array $items, string $type = 'select'): array { return array_map(static function (array|SelectItem $item) use ($type): SelectItem { if ($item instanceof SelectItem) { return $item; } return SelectItem::fromTcaItemArray($item, $type); }, $items); } public function transformSelectItemsToArray(array $items): array { return array_map(static function (array|SelectItem $item): array { if (!$item instanceof SelectItem) { return $item; } return $item->toArray(); }, $items); } /** * Sort given items by label or value or a custom user function built like * "MyVendor\MyExtension\TcaSorter->sortItems" or a callable. * * @param SelectItem[] $items * @param array $sortOrders should be something like like [label => desc] * @return SelectItem[] the sorted items */ private function sortItems(array $items, array $sortOrders): array { foreach ($sortOrders as $order => $direction) { switch ($order) { case 'label': $direction = strtolower($direction); $collator = new \Collator((string)($this->getLanguageService()->getLocale() ?? 'en')); @usort( $items, static function (SelectItem $item1, SelectItem $item2) use ($direction, $collator) { if ($direction === 'desc') { return $collator->compare($item1->getLabel(), $item2->getLabel()) <= 0; } return $collator->compare($item1->getLabel(), $item2->getLabel()); } ); break; case 'value': $direction = strtolower($direction); $collator = new \Collator((string)($this->getLanguageService()->getLocale() ?? 'en')); @usort( $items, static function (SelectItem $item1, SelectItem $item2) use ($direction, $collator) { if ($direction === 'desc') { return ($collator->compare((string)$item1->getValue(), (string)$item2->getValue()) <= 0) ? 1 : 0; } return $collator->compare((string)$item1->getValue(), (string)$item2->getValue()); } ); break; default: $reference = null; GeneralUtility::callUserFunction($direction, $items, $reference); } } return $items; } private function getLanguageService(): LanguageService { return $GLOBALS['LANG'] ?? $this->languageServiceFactory->createFromUserPreferences($this->getBackendUser()); } private function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } }