moduleTemplateFactory->create($request); * $docHeader = $view->getDocHeaderComponent(); * * // Set breadcrumb for a page * $docHeader->setPageBreadcrumb($pageInfo); * * // Add action buttons using ComponentFactory * $buttonBar = $docHeader->getButtonBar(); * $saveButton = $this->componentFactory->createSaveButton('editform'); * $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 1); * } * ``` */ #[Autoconfigure(public: true)] class DocHeaderComponent { /** * Button bar component for managing action buttons. */ protected ButtonBar $buttonBar; /** * Breadcrumb component for rendering navigation trails. */ protected Breadcrumb $breadcrumb; /** * Context information for breadcrumb rendering. * * Contains the main context (page, record, or resource) and optional suffix nodes * for additional navigation elements. */ protected ?BreadcrumbContext $breadcrumbContext = null; /** * Whether the document header is enabled and should be rendered. */ protected bool $enabled = true; /** * Language selector component. */ protected ?ComponentInterface $languageSelector = null; /** * The automatic shortcut button instance, if configured. */ protected ?ShortcutButton $automaticShortcutButton = null; /** * Whether the automatic reload button should be added. */ protected bool $automaticReloadButton = true; public function __construct( protected readonly MenuRegistry $menuRegistry, protected readonly BreadcrumbFactory $breadcrumbFactory, protected readonly ComponentFactory $componentFactory, ) { $this->buttonBar = GeneralUtility::makeInstance(ButtonBar::class); $this->breadcrumb = GeneralUtility::makeInstance(Breadcrumb::class); } /** * Sets the breadcrumb context for rendering. * * This is the main API for providing breadcrumb information. * * For common scenarios, use the convenience methods instead: * - setPageBreadcrumb() for page records * - setRecordBreadcrumb() for any record * - setResourceBreadcrumb() for files or folders * * @param BreadcrumbContext|null $breadcrumbContext The breadcrumb context */ public function setBreadcrumbContext(?BreadcrumbContext $breadcrumbContext): void { $this->breadcrumbContext = $breadcrumbContext; } /** * Sets breadcrumb from a page record array. * * Example: * $view->getDocHeaderComponent()->setPageBreadcrumb($pageInfo); * * @param array $pageRecord The page record array (must contain 'uid') */ public function setPageBreadcrumb(array $pageRecord): void { $this->breadcrumbContext = $this->breadcrumbFactory->forPageArray($pageRecord); } /** * Sets breadcrumb for editing a record. * * Example: * $view->getDocHeaderComponent()->setRecordBreadcrumb('tt_content', 123); * * @param string $table The table name * @param int $uid The record UID */ public function setRecordBreadcrumb(string $table, int $uid): void { $this->breadcrumbContext = $this->breadcrumbFactory->forEditAction($table, $uid); } /** * Sets breadcrumb for any resource (file or folder). * * Example: * $view->getDocHeaderComponent()->setResourceBreadcrumb($file); * $view->getDocHeaderComponent()->setResourceBreadcrumb($folder); * * @param ResourceInterface $resource The resource (file or folder) */ public function setResourceBreadcrumb(ResourceInterface $resource): void { $this->breadcrumbContext = $this->breadcrumbFactory->forResource($resource); } /** * Adds a suffix node to the current breadcrumb context. * * Suffix nodes are appended after the main breadcrumb trail and are useful for: * - Indicating "Create New" actions * - Showing "Edit Multiple" states * - Adding custom contextual information * * Example: * * $docHeader->setPageBreadcrumb($pageInfo); * $docHeader->addBreadcrumbSuffixNode( * new BreadcrumbNode( * identifier: 'new', * label: 'Create New Content Element', * icon: 'actions-add' * ) * ); * * Note: This creates or modifies the breadcrumb context. If you need to build * a complete context, use BreadcrumbFactory instead. * * @param BreadcrumbNode $node The node to append */ public function addBreadcrumbSuffixNode(BreadcrumbNode $node): void { if ($this->breadcrumbContext === null) { $this->breadcrumbContext = new BreadcrumbContext(null, [$node]); } else { // Create new context with added suffix node $existingSuffixNodes = $this->breadcrumbContext->suffixNodes; $existingSuffixNodes[] = $node; $this->breadcrumbContext = new BreadcrumbContext( $this->breadcrumbContext->mainContext, $existingSuffixNodes ); } } /** * Returns the menu registry for adding drop-down menus to the document header. */ public function getMenuRegistry(): MenuRegistry { return $this->menuRegistry; } /** * Returns the button bar for adding action buttons to the document header. * * The button bar supports multiple button positions (left, right) and groups * to organize buttons logically. */ public function getButtonBar(): ButtonBar { return $this->buttonBar; } /** * Determines whether this component is enabled and should be rendered. * * When disabled, the entire document header (including breadcrumbs, buttons, * and menus) will not be displayed in the backend module. */ public function isEnabled(): bool { return $this->enabled; } /** * Enables this component for rendering. */ public function enable(): void { $this->enabled = true; } /** * Disables this component to prevent rendering. */ public function disable(): void { $this->enabled = false; } public function setLanguageSelector(?ComponentInterface $component): void { $this->languageSelector = $component; } public function getLanguageSelector(): ?ComponentInterface { return $this->languageSelector; } /** * Sets the context for the automatic shortcut button. * * Controllers can use this method to provide shortcut information without * manually creating and adding the shortcut button. The button will be * automatically added to the button bar in the correct position. * * Example: * * $docHeader->setShortcutContext('site_configuration.edit', sprintf('Edit site: %s', $siteIdentifier), ['site' => $siteIdentifier]); * * @param string $routeIdentifier The route identifier for the shortcut * @param string $displayName The display name shown in the bookmark list * @param array $arguments Optional arguments to include in the shortcut URL */ public function setShortcutContext(string $routeIdentifier, string $displayName, array $arguments = []): void { $this->automaticShortcutButton = $this->componentFactory->createShortcutButton() ->setRouteIdentifier($routeIdentifier) ->setDisplayName($displayName) ->setArguments($arguments); } /** * Disables the automatic reload button for this module. * * Use this if your module needs custom reload behavior or should not * have a reload button at all. */ public function disableAutomaticReloadButton(): void { $this->automaticReloadButton = false; } /** * Disables the automatic shortcut button for this module. * * Use this if your module should not have a shortcut button. */ public function disableAutomaticShortcutButton(): void { $this->automaticShortcutButton = null; } /** * Returns the complete document header content as an array for rendering. * * This method aggregates all components (buttons, breadcrumbs) into * a structured array that can be consumed by the Fluid template rendering * the backend module layout. * * The returned array structure: * - 'enabled': Whether the document header should be rendered * - 'buttons': Array of button configurations from the button bar * - 'breadcrumb': Breadcrumb trail data from the breadcrumb context * - 'languageSelector': Language Selector */ public function docHeaderContent(?ServerRequestInterface $request): array { // Process MenuRegistry and add any menus as dropdown buttons to the button bar $moduleMenuButton = $this->processMenuRegistry(); if ($moduleMenuButton !== null) { $this->buttonBar->addButton($moduleMenuButton, ButtonBar::BUTTON_POSITION_LEFT, 0); } // Add automatic buttons (reload, shortcut) $this->addAutomaticButtons($request); return [ 'enabled' => $this->isEnabled(), 'buttons' => $this->buttonBar->getButtons($request), 'breadcrumb' => $this->breadcrumb->getBreadcrumb($request, $this->breadcrumbContext), 'languageSelector' => $this->getLanguageSelector(), ]; } /** * Adds automatic reload and shortcut buttons to the button bar. * * This method is called automatically by docHeaderContent() and handles: * - Adding automatic reload button (if enabled) * - Adding automatic shortcut button (if configured) * * The buttons are added to groups 90 and 91 on the right side, which are conventionally * used for these system buttons. This ensures they appear at the end of the button bar * while still allowing PSR-14 event listeners to modify or remove them via ModifyButtonBarEvent. */ private function addAutomaticButtons(?ServerRequestInterface $request): void { if ($request === null) { return; } // Add automatic reload button if enabled if ($this->automaticReloadButton) { $reloadButton = $this->componentFactory->createReloadButton( $request->getAttribute('normalizedParams')->getRequestUri() ); // Add to group 90 on the right (conventionally second-to-last position) $this->buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT, 90); } // Add automatic shortcut button if configured if ($this->automaticShortcutButton !== null) { // Add to group 91 on the right (conventionally last position) $this->buttonBar->addButton($this->automaticShortcutButton); } } /** * Processes registered menus from the MenuRegistry into a dropdown button component. * * Takes the first registered menu from the MenuRegistry and creates a dropdown button * component that can be added to the button bar. * * @return ButtonInterface|null The dropdown button, or null if no menus registered */ private function processMenuRegistry(): ?ButtonInterface { $menus = $this->menuRegistry->getMenus(); if ($menus === []) { return null; } if (count($menus) > 1) { throw new \RuntimeException('The menuRegistry should only contain one menu. ' . 'Multiple DocHeaderComponents can not be displayed - prefer to add distinct dropdown ' . 'buttons to add more view possibilities, or create actual submodules instead of secondary menus.', 1783447740); } // Use the first menu (most controllers only register one menu) $menu = reset($menus); // Hide menu if it's either empty or offers only one item if (count($menu->getMenuItems()) < 2) { return null; } $label = $menu->getLabel(); $dropdownButton = $this->componentFactory->createDropDownButton() ->setShowActiveLabelText(true) ->setShowLabelText(true); foreach ($menu->getMenuItems() as $menuItem) { if ($label === '') { // Previously, the menu was rendered as a