TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:00 +02:00
commit f9941541b7
1178 changed files with 135377 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
<?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\RecordList;
use TYPO3\CMS\Core\Utility\GeneralUtility;
final class DownloadPreset
{
public function __construct(
private readonly string $label,
private readonly array $columns,
private string $identifier = '',
) {
$this->identifier = $identifier ?: md5($label . implode($columns));
}
public function getIdentifier(): string
{
return $this->identifier;
}
public function getLabel(): string
{
return $this->label;
}
public function getColumns(): array
{
return $this->columns;
}
public static function create(array $preset): self
{
$label = $preset['label'] ?? '';
if (is_array($preset['columns'] ?? null)) {
$columns = $preset['columns'];
} else {
$columns = GeneralUtility::trimExplode(',', $preset['columns'] ?? '', true);
}
// Presets with empty columns or empty label are ignored
if ($columns === [] || $label === '') {
throw new \InvalidArgumentException('Invalid download preset.', 1718195273);
}
return new self(
$label,
$columns,
$preset['identifier'] ?? '',
);
}
}
+135
View File
@@ -0,0 +1,135 @@
<?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\RecordList;
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
/**
* Fetches all records like in the records module but returns them as array in order to allow
* downloads (e.g. CSV) in the Controller with prepared data.
*
* This class acts as a composition-based wrapper for DatabaseRecordList for creating records
* ready to be downloaded.
*
* @internal this class is not part of the TYPO3 Core API due to its nature as being a wrapper for DatabaseRecordList and a very specific implementation.
*/
class DownloadRecordList
{
public function __construct(
protected DatabaseRecordList $recordList,
protected TranslationConfigurationProvider $translationConfigurationProvider,
protected TcaSchemaFactory $tcaSchemaFactory
) {}
/**
* Add header line with field names.
*
* @param string[] $columnsToRender
* @return array the columns to be used / shown.
*/
public function getHeaderRow(array $columnsToRender): array
{
// @todo: array_combine() was used in the initial revision already,
// probably to filter out illegal values? Looks odd, but may be due to CSV quirks?
return array_combine($columnsToRender, $columnsToRender);
}
/**
* Fetches records including translations (if not hidden) from the database in the specified order given by
* DatabaseRecordList and returns the prepared records ready to be rendered.
*
* @param string $table the TCA table
* @param string[] $columnsToRender
* @param BackendUserAuthentication $backendUser the current backend user needed to check for permissions
* @param bool $rawValues Whether the field values should not be processed
* @return array[] an array of rows ready to be output
*/
public function getRecords(
string $table,
array $columnsToRender,
BackendUserAuthentication $backendUser,
bool $hideTranslations = false,
bool $rawValues = false
): array {
// Creating the list of fields to include in the SQL query
$selectFields = $this->recordList->getFieldsToSelect($table, $columnsToRender);
$queryResult = $this->recordList->getQueryBuilder($table, $selectFields)->executeQuery();
$schema = $this->tcaSchemaFactory->get($table);
$result = [];
$languageField = $schema->isLanguageAware() ? $schema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName() : null;
// Render items
while ($row = $queryResult->fetchAssociative()) {
// In offline workspace, look for alternative record
BackendUtility::workspaceOL($table, $row, $backendUser->workspace, true);
if (!is_array($row)) {
continue;
}
$result[] = $this->prepareRow($table, $row, $columnsToRender, $rawValues);
if (!$schema->isLanguageAware()) {
continue;
}
if ($hideTranslations) {
continue;
}
// Guard clause so we can quickly return if a record is localized to "all languages"
// It should only be possible to localize a record off default (uid 0)
if ((int)$row[$languageField] === -1) {
continue;
}
$translationsRaw = $this->translationConfigurationProvider->translationInfo($table, $row['uid'], 0, $row, $selectFields);
foreach ($translationsRaw['translations'] ?? [] as $languageId => $translationRow) {
// In offline workspace, look for alternative record
BackendUtility::workspaceOL($table, $translationRow, $backendUser->workspace, true);
if (is_array($translationRow) && $backendUser->checkLanguageAccess($languageId)) {
$result[] = $this->prepareRow($table, $translationRow, $columnsToRender, $rawValues);
}
}
}
return $result;
}
/**
* Prepares a DB row to process the values and maps the values to the columns to render
* to have the same output.
*
* @param string $table Table name
* @param array $row Current record
* @param string[] $columnsToRender the columns to be displayed / downloaded
* @param bool $rawValues Whether the field values should not be processed
* @return array the prepared row
*/
protected function prepareRow(string $table, array $row, array $columnsToRender, bool $rawValues): array
{
$schema = $this->tcaSchemaFactory->get($table);
$labelFieldName = $schema->getCapability(TcaSchemaCapability::Label)->getPrimaryFieldName() ?? '';
foreach ($columnsToRender as $columnName) {
if (!$rawValues) {
if ($columnName === $labelFieldName) {
$row[$columnName] = BackendUtility::getRecordTitle($table, $row);
} elseif ($columnName !== 'pid') {
$row[$columnName] = BackendUtility::getProcessedValueExtra($table, $columnName, $row[$columnName], 0, $row['uid'], false, 0, $row);
}
}
}
return array_intersect_key($row, array_flip($columnsToRender));
}
}
@@ -0,0 +1,127 @@
<?php
/*
* 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\RecordList;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\DataHandling\TableColumnType;
use TYPO3\CMS\Core\Domain\RecordInterface;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Displays the page tree for browsing database records.
*/
#[Autoconfigure(public: true, shared: false)]
class ElementBrowserRecordList extends DatabaseRecordList
{
/**
* Table name of the field pointing to this element browser
*/
protected string $relatingTable = '';
/**
* Field name of the field pointing to this element browser
*/
protected string $relatingField = '';
/**
* Returns the title (based on $code) of a record (from table $table) with the proper link around (that is for "pages"-records a link to the level of that record...)
*/
public function linkWrapItems(string $table, int $uid, string $code, RecordInterface $record): string
{
$row = $record->getRawRecord()->toArray();
if (!$code) {
$code = '<i>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</i>';
} else {
$code = BackendUtility::getRecordTitlePrep($code);
}
$title = BackendUtility::getRecordTitle($table, $row);
$ATag = '<a href="#" data-close="0" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:addToList')) . '">';
$ATag_alt = '<a href="#" data-close="1" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:addToList')) . '">';
$ATag_e = '</a>';
$out = '<span data-uid="' . htmlspecialchars($row['uid']) . '" data-table="' . htmlspecialchars($table) . '" data-title="' . htmlspecialchars($title) . '">';
$out .= $ATag . $this->iconFactory->getIcon('actions-plus', IconSize::SMALL)->render() . $ATag_e . $ATag_alt . $code . $ATag_e;
$out .= '</span>';
return $out;
}
/**
* Check if all row listing conditions are fulfilled.
*
* @param RecordInterface $record Record
* @return bool True, if all conditions are fulfilled.
*/
protected function isRowListingConditionFulfilled(RecordInterface $record): bool
{
$table = $record->getMainType();
$returnValue = true;
if (!$this->relatingField) {
return true;
}
if (!$this->relatingTable) {
return true;
}
$schema = $this->tcaSchemaFactory->get($this->relatingTable);
$field = $schema->getField($this->relatingField);
$tcaFieldConfig = $field->getConfiguration();
foreach ($tcaFieldConfig['filter'] ?? [] as $filter) {
if (!$filter['userFunc']) {
continue;
}
$parameters = $filter['parameters'] ?? [];
$parameters['values'] = [$table . '_' . $record->getUid()];
$parameters['tcaFieldConfig'] = $tcaFieldConfig;
$valueArray = GeneralUtility::callUserFunction($filter['userFunc'], $parameters, $this);
if (empty($valueArray)) {
$returnValue = false;
}
}
if ($field->isType(TableColumnType::FILE)) {
/** @var FileExtensionFilter $fileExtensionFilter */
$fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class);
$valueArray = $fileExtensionFilter->filter(
[$table . '_' . $record->getUid()],
(string)($tcaFieldConfig['allowed'] ?? ''),
(string)($tcaFieldConfig['disallowed'] ?? ''),
);
if ($valueArray === []) {
$returnValue = false;
}
}
return $returnValue;
}
/**
* Set which pointing field (in the TCEForm) we are currently rendering the element browser for
*
* @param string $tableName Table name
* @param string $fieldName Field name
*/
public function setRelatingTableAndField(string $tableName, string $fieldName): void
{
// Check validity of the input data
if ($this->tcaSchemaFactory->has($tableName)) {
$this->relatingTable = $tableName;
if ($this->tcaSchemaFactory->get($tableName)->hasField($fieldName)) {
$this->relatingField = $fieldName;
}
}
}
}
@@ -0,0 +1,82 @@
<?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\RecordList\Event;
use TYPO3\CMS\Backend\RecordList\DatabaseRecordList;
use TYPO3\CMS\Core\Domain\RecordInterface;
/**
* An event to modify the record data for a table in the RecordList.
*/
final class AfterRecordListRowPreparedEvent
{
public function __construct(
private readonly string $table,
private readonly RecordInterface $record,
private array $data,
private readonly DatabaseRecordList $recordList,
private readonly ?string $recTitle,
private readonly array|bool $lockInfo,
private array $tagAttributes,
) {}
public function getTable(): string
{
return $this->table;
}
public function getRecord(): RecordInterface
{
return $this->record;
}
public function getData(): array
{
return $this->data;
}
public function setData(array $data): void
{
$this->data = $data;
}
public function getRecTitle(): ?string
{
return $this->recTitle;
}
public function getLockInfo(): bool|array
{
return $this->lockInfo;
}
public function getRecordList(): DatabaseRecordList
{
return $this->recordList;
}
public function getTagAttributes(): array
{
return $this->tagAttributes;
}
public function setTagAttributes(array $tagAttributes): void
{
$this->tagAttributes = $tagAttributes;
}
}
@@ -0,0 +1,111 @@
<?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\RecordList\Event;
use Psr\Http\Message\ServerRequestInterface;
/**
* Listeners to this event are able to manipulate the download of records, usually triggered via Content > Record.
*/
final class BeforeRecordDownloadIsExecutedEvent
{
/**
* @param array $headerRow - Array of downloaded header metadata
* @param array $records - Array of the actual data
* @param ServerRequestInterface $request - PSR request context (for the actual download request)
* @param string $table - Name of the originating database table
* @param string $format - Format of the exported data (JSON/CSV)
* @param string $filename - Name of the exported file for download
* @param int $id - Page uid from where records are fetched
* @param array $modTSconfig - Currently applied TS config when exporting
* @param array $columnsToRender - Array of selected columns that were fetched
* @param bool $hideTranslations - Hide translations?
*/
public function __construct(
private array $headerRow,
private array $records,
private readonly ServerRequestInterface $request,
private readonly string $table,
private readonly string $format,
private readonly string $filename,
private readonly int $id,
private readonly array $modTSconfig,
private readonly array $columnsToRender,
private readonly bool $hideTranslations,
) {}
public function getHeaderRow(): array
{
return $this->headerRow;
}
public function setHeaderRow(array $headerRow): void
{
$this->headerRow = $headerRow;
}
public function getRecords(): array
{
return $this->records;
}
public function setRecords(array $records): void
{
$this->records = $records;
}
public function getRequest(): ServerRequestInterface
{
return $this->request;
}
public function getTable(): string
{
return $this->table;
}
public function getFormat(): string
{
return $this->format;
}
public function getFilename(): string
{
return $this->filename;
}
public function getId(): int
{
return $this->id;
}
public function getModTSconfig(): array
{
return $this->modTSconfig;
}
public function getColumnsToRender(): array
{
return $this->columnsToRender;
}
public function isHideTranslations(): bool
{
return $this->hideTranslations;
}
}
@@ -0,0 +1,90 @@
<?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\RecordList\Event;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\RecordList\DownloadPreset;
/**
* Event to manipulate the available list of download presets.
*
* Array $presets contains a list of DownloadPreset objects
* with their methods: `getIdentifier()`, `getLabel()` and `getColumns()`.
*
* The event is always coupled to a specific database table.
*/
final class BeforeRecordDownloadPresetsAreDisplayedEvent
{
/** @var DownloadPreset[] */
private array $presets;
/**
* @param string $table - Name of the originating database table
* @param array<string|int, array{columns: string|string[]|null, label: string|null}> $presets - Contains list of sub-arrays with keys "label" (string, name of the preset) and "columns" (string, comma-separated list of columns included in the preset)
* @param ServerRequestInterface $request - Request-context of the action that displays the preset
* @param int $id - Page ID where the records are stored
*/
public function __construct(
private readonly string $table,
array $presets,
private readonly ServerRequestInterface $request,
private readonly int $id,
) {
$this->setPresets($presets);
}
/**
* @return DownloadPreset[]
*/
public function getPresets(): array
{
return $this->presets;
}
public function setPresets(array $presets): void
{
$this->presets = [];
foreach ($presets as $preset) {
if (is_array($preset)) {
try {
$preset = DownloadPreset::create($preset);
} catch (\InvalidArgumentException) {
continue;
}
}
if ($preset instanceof DownloadPreset) {
$this->presets[$preset->getIdentifier()] = $preset;
}
}
}
public function getDatabaseTable(): string
{
return $this->table;
}
public function getRequest(): ServerRequestInterface
{
return $this->request;
}
public function getId(): int
{
return $this->id;
}
}
@@ -0,0 +1,136 @@
<?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\RecordList\Event;
use TYPO3\CMS\Backend\RecordList\DatabaseRecordList;
/**
* An event to modify the header columns for a table in the RecordList
*/
final class ModifyRecordListHeaderColumnsEvent
{
/**
* Additional header attributes for the table header row
*
* @var string[]
*/
private array $headerAttributes = [];
/**
* @param array<int> $recordIds
*/
public function __construct(
private array $columns,
private readonly string $table,
private readonly array $recordIds,
private readonly DatabaseRecordList $recordList
) {}
/**
* Add a new column or override an existing one. Latter is only possible,
* in case $columnName is given. Otherwise, the column will be added with
* a numeric index, which is generally not recommended.
*
* Note: Due to the behaviour of DatabaseRecordList, just adding a column
* does not mean that it is also displayed. The internal $fieldArray needs
* to be adjusted as well. This method only adds the column to the data array.
* Therefore, this method should mainly be used to edit existing columns, e.g.
* change their label.
*/
public function setColumn(string $column, string $columnName = ''): void
{
if ($columnName !== '') {
$this->columns[$columnName] = $column;
} else {
$this->columns[] = $column;
}
}
/**
* Whether the column exists
*/
public function hasColumn(string $columnName): bool
{
return (bool)($this->columns[$columnName] ?? false);
}
/**
* Get column by its name
*
* @return string|null The column or NULL if the column does not exist
*/
public function getColumn(string $columnName): ?string
{
return $this->columns[$columnName] ?? null;
}
/**
* Remove column by its name
*
* @return bool Whether the column could be removed - Will therefore
* return FALSE if the column to remove does not exist.
*/
public function removeColumn(string $columnName): bool
{
if (!isset($this->columns[$columnName])) {
return false;
}
unset($this->columns[$columnName]);
return true;
}
public function setColumns(array $columns): void
{
$this->columns = $columns;
}
public function getColumns(): array
{
return $this->columns;
}
public function setHeaderAttributes(array $headerAttributes): void
{
$this->headerAttributes = $headerAttributes;
}
public function getHeaderAttributes(): array
{
return $this->headerAttributes;
}
public function getTable(): string
{
return $this->table;
}
public function getRecordIds(): array
{
return $this->recordIds;
}
/**
* Returns the current DatabaseRecordList instance.
*
* @todo Might be replaced by a DTO in the future
*/
public function getRecordList(): DatabaseRecordList
{
return $this->recordList;
}
}
@@ -0,0 +1,164 @@
<?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\RecordList\Event;
use Psr\Http\Message\RequestInterface;
use TYPO3\CMS\Backend\RecordList\DatabaseRecordList;
use TYPO3\CMS\Backend\Template\Components\ActionGroup;
use TYPO3\CMS\Backend\Template\Components\ComponentGroup;
use TYPO3\CMS\Backend\Template\Components\ComponentInterface;
use TYPO3\CMS\Core\Domain\RecordInterface;
/**
* An event to modify the displayed record actions (e.g.
* "edit", "copy", "delete") for a table in the RecordList.
*/
final readonly class ModifyRecordListRecordActionsEvent
{
public function __construct(
private ComponentGroup $primary,
private ComponentGroup $secondary,
private RecordInterface $record,
private DatabaseRecordList $recordList,
private RequestInterface $request,
) {}
/**
* Add a new action or override an existing one. Latter is only possible,
* in case $columnName is given. Otherwise, the column will be added with
* a numeric index, which is generally not recommended. It's also possible
* to define the position of an action with either the "before" or "after"
* argument, while their value must be an existing action.
*
* Note: In case non or an invalid $group is provided, the new action will
* be added to the secondary group.
*
* @param ?ComponentInterface $action
* @param string $actionName
* @param ActionGroup $group
* @param string $before
* @param string $after
*/
public function setAction(
?ComponentInterface $action,
string $actionName,
ActionGroup $group = ActionGroup::secondary,
string $before = '',
string $after = '',
): void {
if ($actionName === '') {
throw new \Exception('You must provide a valid action name when adding a new action.', 1761584690);
}
$componentGroup = match ($group) {
ActionGroup::primary => $this->primary,
ActionGroup::secondary => $this->secondary,
};
$componentGroup->add($actionName, $action, $before, $after);
}
/**
* Whether the action exists in the given group. In case non or
* an invalid $group is provided, both groups will be checked.
*/
public function hasAction(string $actionName, ?ActionGroup $group = null): bool
{
return match ($group) {
ActionGroup::primary => $this->primary->has($actionName),
ActionGroup::secondary => $this->secondary->has($actionName),
null => $this->primary->has($actionName) || $this->secondary->has($actionName),
};
}
/**
* Get action by its name. In case the action exists in both groups
* and non or an invalid $group is provided, the action from the
* "primary" group will be returned.
*/
public function getAction(string $actionName, ?ActionGroup $group = null): ?ComponentInterface
{
return match ($group) {
ActionGroup::primary => $this->primary->get($actionName),
ActionGroup::secondary => $this->secondary->get($actionName),
null => $this->primary->get($actionName) ?? $this->secondary->get($actionName),
};
}
/**
* Remove action by its name. In case the action exists in both groups
* and non or an invalid $group is provided, the action will be removed
* from both groups.
*/
public function removeAction(string $actionName, ?ActionGroup $group = null): void
{
if ($group === null) {
$this->primary->remove($actionName);
$this->secondary->remove($actionName);
return;
}
match ($group) {
ActionGroup::primary => $this->primary->remove($actionName),
ActionGroup::secondary => $this->secondary->remove($actionName),
};
}
public function moveActionTo(
string $actionName,
ActionGroup $group,
string $before = '',
string $after = '',
): void {
if (!$this->hasAction($actionName)) {
throw new \RuntimeException('The action "' . $actionName . '" does not exist and therefore cannot be moved.', 1761646464);
}
$action = $this->getAction($actionName);
$this->removeAction($actionName);
$this->setAction($action, $actionName, $group, $before, $after);
}
/**
* Get the actions of a specific group
*/
public function getActionGroup(ActionGroup $group): ComponentGroup
{
return match ($group) {
ActionGroup::primary => $this->primary,
ActionGroup::secondary => $this->secondary,
};
}
public function getRecord(): RecordInterface
{
return $this->record;
}
/**
* Returns the current DatabaseRecordList instance.
*
* @todo Might be replaced by a DTO in the future
*/
public function getRecordList(): DatabaseRecordList
{
return $this->recordList;
}
public function getRequest(): RequestInterface
{
return $this->request;
}
}
@@ -0,0 +1,147 @@
<?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\RecordList\Event;
use TYPO3\CMS\Backend\RecordList\DatabaseRecordList;
/**
* An event to modify the multi record selection actions (e.g.
* "edit", "copy to clipboard") for a table in the RecordList.
*/
final class ModifyRecordListTableActionsEvent
{
/**
* The label, which will be displayed in case
* no action is available for current the user.
*/
private string $noActionLabel = '';
/**
* @param array<int> $recordIds
*/
public function __construct(
private array $actions,
private readonly string $table,
private readonly array $recordIds,
private readonly DatabaseRecordList $recordList
) {}
/**
* Add a new action or override an existing one. Latter is only possible,
* in case $actionName is given. Otherwise, the action will be added with
* a numeric index, which is generally not recommended. It's also possible
* to define the position of an action with either the "before" or "after"
* argument, while their value must be an existing action.
*/
public function setAction(string $action, string $actionName = '', string $before = '', string $after = ''): void
{
if ($actionName !== '') {
if ($before !== '' && $this->hasAction($before)) {
$end = array_splice($this->actions, (int)(array_search($before, array_keys($this->actions), true)));
$this->actions = array_merge($this->actions, [$actionName => $action], $end);
} elseif ($after !== '' && $this->hasAction($after)) {
$end = array_splice($this->actions, (int)(array_search($after, array_keys($this->actions), true)) + 1);
$this->actions = array_merge($this->actions, [$actionName => $action], $end);
} else {
$this->actions[$actionName] = $action;
}
} else {
$this->actions[] = $action;
}
}
/**
* Whether the action exists
*/
public function hasAction(string $actionName): bool
{
return (bool)($this->actions[$actionName] ?? false);
}
/**
* Get action by its name
*
* @return string|null The action or NULL if the action does not exist
*/
public function getAction(string $actionName): ?string
{
return $this->actions[$actionName] ?? null;
}
/**
* Remove action by its name
*
* @return bool Whether the action could be removed - Will therefore
* return FALSE if the action to remove does not exist.
*/
public function removeAction(string $actionName): bool
{
if (!isset($this->actions[$actionName])) {
return false;
}
unset($this->actions[$actionName]);
return true;
}
public function setActions(array $actions): void
{
$this->actions = $actions;
}
public function getActions(): array
{
return $this->actions;
}
public function setNoActionLabel(string $noActionLabel): void
{
$this->noActionLabel = $noActionLabel;
}
/**
* Get the label, which will be displayed, in case no
* action is available for the current user. Note: If
* this returns an empty string, this only means that
* no other listener set a label before. TYPO3 will
* always fall back to a default if this remains empty.
*/
public function getNoActionLabel(): string
{
return $this->noActionLabel;
}
public function getTable(): string
{
return $this->table;
}
public function getRecordIds(): array
{
return $this->recordIds;
}
/**
* Returns the current DatabaseRecordList instance.
*
* @todo Might be replaced by a DTO in the future
*/
public function getRecordList(): DatabaseRecordList
{
return $this->recordList;
}
}