Files

208 lines
7.2 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Template\Components;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface;
/**
* Container for managing buttons in the backend module document header.
* The ButtonBar organizes buttons into two positions (left and right) with multiple groups,
* allowing precise control over button placement and visual grouping.
*
* Buttons are organized in a three-level structure:
* 1. Position: LEFT or RIGHT side of the header
* 2. Group: Numerical groups within each position (1, 2, 3, etc.)
* 3. Individual buttons within each group
*
* Groups are rendered with visual spacing between them, allowing logical grouping
* of related actions. Lower group numbers appear first (left-to-right, or top-to-bottom on mobile).
*
* Example - Using pre-configured buttons from ComponentFactory:
*
* ```
* public function __construct(
* protected readonly ComponentFactory $componentFactory,
* ) {}
*
* public function myAction(): ResponseInterface
* {
* $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
*
* // Use pre-configured save button
* $saveButton = $this->componentFactory->createSaveButton('editform');
* $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
*
* // Use pre-configured back button
* $backButton = $this->componentFactory->createBackButton($returnUrl);
* $buttonBar->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
* }
* ```
*
* Example - Creating custom buttons with ComponentFactory:
*
* ```
* public function __construct(
* protected readonly ComponentFactory $componentFactory,
* ) {}
*
* public function myAction(): ResponseInterface
* {
* $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
*
* // Create custom link button
* $customButton = $this->componentFactory->createLinkButton()
* ->setHref('/custom-action')
* ->setTitle('Custom Action')
* ->setIcon($iconFactory->getIcon('actions-custom'));
* $buttonBar->addButton($customButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
* }
* ```
*
* Example - Buttons with automatic positioning:
*
* ```
* // ShortcutButton implements PositionInterface and positions itself automatically
* $shortcutButton = $this->componentFactory->createShortcutButton()
* ->setRouteIdentifier('my_module')
* ->setDisplayName('My Module');
* $buttonBar->addButton($shortcutButton); // Position and group are automatic
* ```
*
* Example - Dropdown button:
*
* ```
* $dropdownButton = $this->componentFactory->createDropDownButton()
* ->setLabel('Actions')
* ->setIcon($iconFactory->getIcon('actions-menu'));
*
* $item1 = $this->componentFactory->createDropDownItem()
* ->setLabel('Edit')
* ->setHref('/edit');
* $dropdownButton->addItem($item1);
*
* $item2 = $this->componentFactory->createDropDownItem()
* ->setLabel('Delete')
* ->setHref('/delete');
* $dropdownButton->addItem($item2);
*
* $buttonBar->addButton($dropdownButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
* ```
*
* @phpstan-type Buttons array<self::BUTTON_POSITION_*, array<int, list<ButtonInterface>>>
*/
#[Autoconfigure(public: true)]
class ButtonBar
{
/**
* Position constant for left side of the button bar
*/
public const BUTTON_POSITION_LEFT = 'left';
/**
* Position constant for right side of the button bar
*/
public const BUTTON_POSITION_RIGHT = 'right';
/**
* Internal array of all registered buttons
*
* @var Buttons
*/
protected array $buttons = [];
public function __construct(
protected readonly EventDispatcherInterface $eventDispatcher,
) {}
/**
* Add a new button
*
* Buttons implementing PositionInterface will automatically use their own
* predefined position and group, ignoring the $buttonPosition and $buttonGroup
* parameters. This ensures buttons like ShortcutButton always appear in their
* designated location.
*
* @param ButtonInterface $button The Button Object to add
* @param self::BUTTON_POSITION_* $buttonPosition Position of the button (left/right). Ignored if button implements PositionInterface.
* @param int $buttonGroup Buttongroup of the button. Ignored if button implements PositionInterface.
*
* @throws \InvalidArgumentException In case a button is not valid
*/
public function addButton(
ButtonInterface $button,
string $buttonPosition = self::BUTTON_POSITION_LEFT,
int $buttonGroup = 1
): static {
if (!$button->isValid()) {
throw new \InvalidArgumentException('Button "' . $button->getType() . '" is not valid', 1441706370);
}
// Buttons implementing PositionInterface define their own position and group
if ($button instanceof PositionInterface) {
$buttonPosition = $button->getPosition();
$buttonGroup = $button->getGroup();
}
// We make the button immutable here
$this->buttons[$buttonPosition][$buttonGroup][] = clone $button;
return $this;
}
/**
* Returns an associative array of all buttons in the form of
* ButtonPosition > ButtonGroup > Button
*/
public function getButtons(ServerRequestInterface $request): array
{
// here we need to call the sorting methods and stuff.
foreach ($this->buttons as $position => $_) {
ksort($this->buttons[$position]);
}
// Dispatch event for manipulating the docHeaderButtons
$this->buttons = $this->eventDispatcher->dispatch(new ModifyButtonBarEvent($this->buttons, $this, $request))->getButtons();
return $this->buttons;
}
/**
* Checks whether a button of the specified type has been added to the button bar.
*
* This is primarily used for backwards compatibility detection to prevent
* duplicate automatic buttons when controllers manually add them.
*
* @param class-string<ButtonInterface> $buttonClassName Fully qualified button class name
* @return bool True if a button of this type exists, false otherwise
*/
public function hasButtonOfType(string $buttonClassName): bool
{
foreach ($this->buttons as $groups) {
foreach ($groups as $groupButtons) {
foreach ($groupButtons as $button) {
if ($button instanceof $buttonClassName) {
return true;
}
}
}
}
return false;
}
}