Skip to content

Commit bbe6f92

Browse files
Kantibmack
authored andcommitted
[!!!][TASK] Rework actions to use Buttons API
This is an alternativ approach trying to combine: https://review.typo3.org/c/Packages/TYPO3.CMS/+/90383 https://review.typo3.org/c/Packages/TYPO3.CMS/+/89733 https://review.typo3.org/c/Packages/TYPO3.CMS/+/91116 The overall goal is to: 1. Use the Button objects / Components over strings to collect, process (via events) the primary and secondary buttons in List module 2. Utilize the same functionality in File list module 3. Streamline both events to bring the functionality for extension authors closer together. Main integrations points: * Use the new ComponentFactory * Rework the ModifyRecordListRecordActionsEvent to use ComponentInterface instead of strings * Add ComponentGroup to better handle the actions grouped into primary and secondary lists of buttons Resolves: #107884 Related: #107824 Releases: main Change-Id: Id4afb0a368e70d5f6ece8b5a7decd2e4bad1244b Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91288 Tested-by: core-ci <typo3@b13.com> Reviewed-by: Garvin Hicking <garvin@hick.ing> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Garvin Hicking <garvin@hick.ing> Reviewed-by: Benni Mack <benni@typo3.org>
1 parent 733e296 commit bbe6f92

File tree

16 files changed

+1117
-412
lines changed

16 files changed

+1117
-412
lines changed

typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php

Lines changed: 255 additions & 262 deletions
Large diffs are not rendered by default.

typo3/sysext/backend/Classes/RecordList/Event/ModifyRecordListRecordActionsEvent.php

Lines changed: 69 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,25 @@
1717

1818
namespace TYPO3\CMS\Backend\RecordList\Event;
1919

20+
use Psr\Http\Message\RequestInterface;
2021
use TYPO3\CMS\Backend\RecordList\DatabaseRecordList;
22+
use TYPO3\CMS\Backend\Template\Components\ActionGroup;
23+
use TYPO3\CMS\Backend\Template\Components\ComponentGroup;
24+
use TYPO3\CMS\Backend\Template\Components\ComponentInterface;
25+
use TYPO3\CMS\Core\Domain\RecordInterface;
2126

2227
/**
2328
* An event to modify the displayed record actions (e.g.
2429
* "edit", "copy", "delete") for a table in the RecordList.
2530
*/
26-
final class ModifyRecordListRecordActionsEvent
31+
final readonly class ModifyRecordListRecordActionsEvent
2732
{
2833
public function __construct(
29-
private array $actions,
30-
private readonly string $table,
31-
private readonly array $record,
32-
private readonly DatabaseRecordList $recordList
34+
private ComponentGroup $primary,
35+
private ComponentGroup $secondary,
36+
private RecordInterface $record,
37+
private DatabaseRecordList $recordList,
38+
private RequestInterface $request,
3339
) {}
3440

3541
/**
@@ -42,118 +48,101 @@ public function __construct(
4248
* Note: In case non or an invalid $group is provided, the new action will
4349
* be added to the secondary group.
4450
*
45-
* @param string $action
51+
* @param ?ComponentInterface $action
4652
* @param string $actionName
47-
* @param string $group
53+
* @param ActionGroup $group
4854
* @param string $before
4955
* @param string $after
5056
*/
5157
public function setAction(
52-
string $action,
53-
string $actionName = '',
54-
string $group = '',
58+
?ComponentInterface $action,
59+
string $actionName,
60+
ActionGroup $group = ActionGroup::secondary,
5561
string $before = '',
56-
string $after = ''
62+
string $after = '',
5763
): void {
58-
// Only "primary" and "secondary" are valid, default to "secondary" otherwise
59-
$group = in_array($group, ['primary', 'secondary'], true) ? $group : 'secondary';
60-
61-
if ($actionName !== '') {
62-
if ($before !== '' && $this->hasAction($before, $group)) {
63-
$end = array_splice($this->actions[$group], (int)(array_search($before, array_keys($this->actions[$group]), true)));
64-
$this->actions[$group] = array_merge($this->actions[$group], [$actionName => $action], $end);
65-
} elseif ($after !== '' && $this->hasAction($after, $group)) {
66-
$end = array_splice($this->actions[$group], (int)(array_search($after, array_keys($this->actions[$group]), true)) + 1);
67-
$this->actions[$group] = array_merge($this->actions[$group], [$actionName => $action], $end);
68-
} else {
69-
$this->actions[$group][$actionName] = $action;
70-
}
71-
} else {
72-
$this->actions[$group][] = $action;
64+
if ($actionName === '') {
65+
throw new \Exception('You must provide a valid action name when adding a new action.', 1761584690);
7366
}
67+
68+
$componentGroup = match ($group) {
69+
ActionGroup::primary => $this->primary,
70+
ActionGroup::secondary => $this->secondary,
71+
};
72+
$componentGroup->add($actionName, $action, $before, $after);
7473
}
7574

7675
/**
7776
* Whether the action exists in the given group. In case non or
7877
* an invalid $group is provided, both groups will be checked.
7978
*/
80-
public function hasAction(string $actionName, string $group = ''): bool
79+
public function hasAction(string $actionName, ?ActionGroup $group = null): bool
8180
{
82-
if (in_array($group, ['primary', 'secondary'], true)) {
83-
return (bool)($this->actions[$group][$actionName] ?? false);
84-
}
85-
86-
return (bool)($this->actions['primary'][$actionName] ?? $this->actions['secondary'][$actionName] ?? false);
81+
return match ($group) {
82+
ActionGroup::primary => $this->primary->has($actionName),
83+
ActionGroup::secondary => $this->secondary->has($actionName),
84+
null => $this->primary->has($actionName) || $this->secondary->has($actionName),
85+
};
8786
}
8887

8988
/**
9089
* Get action by its name. In case the action exists in both groups
9190
* and non or an invalid $group is provided, the action from the
9291
* "primary" group will be returned.
9392
*/
94-
public function getAction(string $actionName, string $group = ''): ?string
93+
public function getAction(string $actionName, ?ActionGroup $group = null): ?ComponentInterface
9594
{
96-
if (in_array($group, ['primary', 'secondary'], true)) {
97-
return $this->actions[$group][$actionName] ?? null;
98-
}
99-
100-
return $this->actions['primary'][$actionName] ?? $this->actions['secondary'][$actionName] ?? null;
95+
return match ($group) {
96+
ActionGroup::primary => $this->primary->get($actionName),
97+
ActionGroup::secondary => $this->secondary->get($actionName),
98+
null => $this->primary->get($actionName) ?? $this->secondary->get($actionName),
99+
};
101100
}
102101

103102
/**
104103
* Remove action by its name. In case the action exists in both groups
105104
* and non or an invalid $group is provided, the action will be removed
106105
* from both groups.
107-
*
108-
* @return bool Whether the action could be removed - Will therefore
109-
* return FALSE if the action to remove does not exist.
110106
*/
111-
public function removeAction(string $actionName, string $group = ''): bool
107+
public function removeAction(string $actionName, ?ActionGroup $group = null): void
112108
{
113-
if (($this->actions[$group][$actionName] ?? false) && in_array($group, ['primary', 'secondary'], true)) {
114-
unset($this->actions[$group][$actionName]);
115-
return true;
116-
}
117-
118-
$actionRemoved = false;
119-
120-
if ($this->actions['primary'][$actionName] ?? false) {
121-
unset($this->actions['primary'][$actionName]);
122-
$actionRemoved = true;
109+
if ($group === null) {
110+
$this->primary->remove($actionName);
111+
$this->secondary->remove($actionName);
112+
return;
123113
}
114+
match ($group) {
115+
ActionGroup::primary => $this->primary->remove($actionName),
116+
ActionGroup::secondary => $this->secondary->remove($actionName),
117+
};
118+
}
124119

125-
if ($this->actions['secondary'][$actionName] ?? false) {
126-
unset($this->actions['secondary'][$actionName]);
127-
$actionRemoved = true;
120+
public function moveActionTo(
121+
string $actionName,
122+
ActionGroup $group,
123+
string $before = '',
124+
string $after = '',
125+
): void {
126+
if (!$this->hasAction($actionName)) {
127+
throw new \RuntimeException('The action "' . $actionName . '" does not exist and therefore cannot be moved.', 1761646464);
128128
}
129-
130-
return $actionRemoved;
129+
$action = $this->getAction($actionName);
130+
$this->removeAction($actionName);
131+
$this->setAction($action, $actionName, $group, $before, $after);
131132
}
132133

133134
/**
134135
* Get the actions of a specific group
135136
*/
136-
public function getActionGroup(string $group): ?array
137-
{
138-
return in_array($group, ['primary', 'secondary'], true) ? $this->actions[$group] : null;
139-
}
140-
141-
public function setActions(array $actions): void
142-
{
143-
$this->actions = $actions;
144-
}
145-
146-
public function getActions(): array
137+
public function getActionGroup(ActionGroup $group): ComponentGroup
147138
{
148-
return $this->actions;
139+
return match ($group) {
140+
ActionGroup::primary => $this->primary,
141+
ActionGroup::secondary => $this->secondary,
142+
};
149143
}
150144

151-
public function getTable(): string
152-
{
153-
return $this->table;
154-
}
155-
156-
public function getRecord(): array
145+
public function getRecord(): RecordInterface
157146
{
158147
return $this->record;
159148
}
@@ -167,4 +156,9 @@ public function getRecordList(): DatabaseRecordList
167156
{
168157
return $this->recordList;
169158
}
159+
160+
public function getRequest(): RequestInterface
161+
{
162+
return $this->request;
163+
}
170164
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Backend\Template\Components;
19+
20+
/**
21+
* Defines groups for record list / file list actions.
22+
* Currently, there are only two of them, which can be used to move Buttons between the Primary Group and the Secondary Group via Events.
23+
*/
24+
enum ActionGroup
25+
{
26+
case primary;
27+
case secondary;
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Backend\Template\Components\Buttons;
19+
20+
enum ButtonSize: string
21+
{
22+
case SMALL = 'btn-sm';
23+
case MEDIUM = '';
24+
case LARGE = 'btn-lg';
25+
}

typo3/sysext/backend/Classes/Template/Components/Buttons/DropDownButton.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class DropDownButton implements ButtonInterface
6363
protected array $items = [];
6464
protected bool $showLabelText = false;
6565
protected bool $disabled = false;
66+
protected ButtonSize $size = ButtonSize::SMALL;
6667

6768
public function getIcon(): ?Icon
6869
{
@@ -136,6 +137,17 @@ public function addItem(DropDownItemInterface $item): static
136137
return $this;
137138
}
138139

140+
public function getSize(): ButtonSize
141+
{
142+
return $this->size;
143+
}
144+
145+
public function setSize(ButtonSize $size): DropDownButton
146+
{
147+
$this->size = $size;
148+
return $this;
149+
}
150+
139151
/**
140152
* @return DropDownItemInterface[]
141153
*/
@@ -175,7 +187,7 @@ public function render(): string
175187

176188
$attributes = [
177189
'type' => 'button',
178-
'class' => 'btn btn-sm btn-default dropdown-toggle',
190+
'class' => 'btn ' . $this->getSize()->value . ' btn-default dropdown-toggle',
179191
'data-bs-toggle' => 'dropdown',
180192
'aria-expanded' => 'false',
181193
];

typo3/sysext/backend/Classes/Template/Components/Buttons/GenericButton.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class GenericButton implements ButtonInterface
9494
protected ?string $title = null;
9595
protected ?string $href = null;
9696
protected string $classes = '';
97+
protected ButtonSize $size = ButtonSize::SMALL;
9798
protected array $attributes = [];
9899
protected bool $showLabelText = false;
99100

@@ -164,6 +165,17 @@ public function setClasses(string $classes): static
164165
return $this;
165166
}
166167

168+
public function getSize(): ButtonSize
169+
{
170+
return $this->size;
171+
}
172+
173+
public function setSize(ButtonSize $size): static
174+
{
175+
$this->size = $size;
176+
return $this;
177+
}
178+
167179
/**
168180
* @param array<string, string> $attributes
169181
*/
@@ -207,7 +219,7 @@ public function getType(): string
207219
protected function getAttributesString(): string
208220
{
209221
$attributes = $this->getAttributes();
210-
$attributes['class'] = rtrim('btn btn-sm btn-default ' . $this->getClasses());
222+
$attributes['class'] = rtrim('btn ' . $this->getSize()->value . ' btn-default ' . $this->getClasses());
211223
if ($this->getHref()) {
212224
$attributes['href'] = $this->getHref();
213225
}

typo3/sysext/backend/Classes/Template/Components/Buttons/LinkButton.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class LinkButton extends AbstractButton
5050

5151
protected string $role = 'button';
5252

53+
protected ButtonSize $size = ButtonSize::SMALL;
54+
5355
public function getHref(): string
5456
{
5557
return $this->href;
@@ -72,6 +74,17 @@ public function setRole(string $role): static
7274
return $this;
7375
}
7476

77+
public function getSize(): ButtonSize
78+
{
79+
return $this->size;
80+
}
81+
82+
public function setSize(ButtonSize $size): static
83+
{
84+
$this->size = $size;
85+
return $this;
86+
}
87+
7588
public function isValid(): bool
7689
{
7790
return trim($this->getHref()) !== ''
@@ -86,7 +99,7 @@ public function render(): string
8699
'role' => $this->getRole(),
87100
'href' => $this->getHref(),
88101
// @see SplitButton - hard-coded replacement for this hard-coded class-list
89-
'class' => 'btn btn-sm btn-default ' . $this->getClasses(),
102+
'class' => 'btn ' . $this->getSize()->value . ' btn-default ' . $this->getClasses(),
90103
'title' => $this->getTitle(),
91104
];
92105
$labelText = '';
@@ -104,7 +117,7 @@ public function render(): string
104117
'<a %s>%s%s</a>',
105118
GeneralUtility::implodeAttributes($attributes, true),
106119
$this->getIcon()?->render() ?? '',
107-
htmlspecialchars($labelText)
120+
htmlspecialchars($labelText),
108121
);
109122
}
110123

0 commit comments

Comments
 (0)