Files

237 lines
8.5 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\Breadcrumb;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Backend\Dto\Breadcrumb\BreadcrumbNode;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Domain\RecordFactory;
use TYPO3\CMS\Core\Domain\RecordInterface;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Resource\ResourceInterface;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
/**
* Factory for creating breadcrumb contexts from controller actions.
*
* This factory centralizes the logic for determining what context to show
* in breadcrumbs based on different controller actions (edit, new, list, etc.).
*
* It handles:
* - Record lookups and validation
* - Creation of "new record" breadcrumb nodes
* - Multi-record edit scenarios
* - Parent record resolution
*
* @internal Subject to change until v15 LTS
*/
final readonly class BreadcrumbFactory
{
public function __construct(
private LoggerInterface $logger,
private RecordFactory $recordFactory,
private IconFactory $iconFactory,
private TcaSchemaFactory $tcaSchemaFactory,
) {}
/**
* Creates breadcrumb context for editing an existing record.
*
* @param string $table The table name
* @param int $uid The record UID
* @return BreadcrumbContext Context containing the record or null on failure
*/
public function forEditAction(string $table, int $uid): BreadcrumbContext
{
$rawRecord = BackendUtility::getRecord($table, $uid);
if ($rawRecord === null) {
$this->logger->warning(
'Failed to load record for breadcrumb',
['table' => $table, 'uid' => $uid]
);
return new BreadcrumbContext(null, []);
}
try {
$record = $this->recordFactory->createResolvedRecordFromDatabaseRow($table, $rawRecord);
return new BreadcrumbContext($record, []);
} catch (\Exception $e) {
// @todo: Catching \Exception here is a code smell, this shouldn't be so generic and can hide away too many issues.
$this->logger->error(
'Failed to create record instance for breadcrumb',
['table' => $table, 'uid' => $uid, 'exception' => $e->getMessage()]
);
return new BreadcrumbContext(null, []);
}
}
/**
* Creates breadcrumb context for editing multiple records.
*
* Shows a generic "Edit Multiple [RecordType]" node instead of individual records.
*
* @param string $table The table name
* @param int $pid The parent page ID
* @return BreadcrumbContext Context with parent page and "edit multiple" suffix node
*/
public function forEditMultipleAction(string $table, int $pid): BreadcrumbContext
{
$parentRecord = $this->getParentPageRecord($pid);
$schema = $this->tcaSchemaFactory->has($table) ? $this->tcaSchemaFactory->get($table) : null;
$recordTypeLabel = $schema?->getTitle($this->getLanguageService()->sL(...))
?? $schema?->getTitle()
?? $table;
$suffixNode = new BreadcrumbNode(
identifier: 'edit-multiple-' . $table,
label: sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editMultiple'),
$recordTypeLabel
),
icon: $this->iconFactory->getIconForRecord($table, [])->getIdentifier(),
);
return new BreadcrumbContext($parentRecord, [$suffixNode]);
}
/**
* Creates breadcrumb context for creating a new record.
*
* @param string $table The table name
* @param int $pid The parent page ID
* @param array $defaults Default values for the new record (used for icon overlay)
* @return BreadcrumbContext Context with parent page and "create new" suffix node
*/
public function forNewAction(string $table, int $pid, array $defaults = []): BreadcrumbContext
{
$parentRecord = $this->getParentPageRecord($pid);
$schema = $this->tcaSchemaFactory->has($table) ? $this->tcaSchemaFactory->get($table) : null;
$recordTypeLabel = $schema?->getTitle($this->getLanguageService()->sL(...))
?? $schema?->getTitle()
?? $table;
try {
$icon = $this->iconFactory->getIconForRecord($table, $defaults);
$suffixNode = new BreadcrumbNode(
identifier: 'new-' . $table,
label: sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew'),
$recordTypeLabel
),
icon: $icon->getIdentifier(),
iconOverlay: 'overlay-new',
);
} catch (\Exception $e) {
$this->logger->warning(
'Failed to create icon for new record breadcrumb',
['table' => $table, 'exception' => $e->getMessage()]
);
$suffixNode = new BreadcrumbNode(
identifier: 'new-' . $table,
label: sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew'),
$recordTypeLabel
),
);
}
return new BreadcrumbContext($parentRecord, [$suffixNode]);
}
/**
* Creates breadcrumb context from a page record array.
*
* Example:
* `$view->getDocHeaderComponent()->setBreadcrumbContext($this->breadcrumbFactory->forPageArray($pageInfo));`
*
* @param array $pageRecord The page record array (must contain 'uid')
* @return BreadcrumbContext Context with the page record or null on failure
*/
public function forPageArray(array $pageRecord): BreadcrumbContext
{
if (!isset($pageRecord['uid'])) {
$this->logger->warning('Page record array must contain uid for breadcrumb');
return new BreadcrumbContext(null, []);
}
try {
$record = $this->recordFactory->createResolvedRecordFromDatabaseRow('pages', $pageRecord);
return new BreadcrumbContext($record, []);
} catch (\Exception $e) {
$this->logger->error(
'Failed to create page record instance for breadcrumb',
['uid' => $pageRecord['uid'], 'exception' => $e->getMessage()]
);
return new BreadcrumbContext(null, []);
}
}
/**
* Creates breadcrumb context for any resource (file or folder).
*
* @param ResourceInterface $resource The resource (file or folder)
* @return BreadcrumbContext Context with the resource
*/
public function forResource(ResourceInterface $resource): BreadcrumbContext
{
return new BreadcrumbContext($resource, []);
}
/**
* Gets the parent page record for a given PID.
*
* @param int $pid The page ID
* @return RecordInterface|null The page record or null if not found/accessible
*/
private function getParentPageRecord(int $pid): ?RecordInterface
{
if ($pid <= 0) {
return null;
}
$rawRecord = BackendUtility::getRecord('pages', $pid);
if ($rawRecord === null) {
$this->logger->warning(
'Failed to load parent page for breadcrumb',
['pid' => $pid]
);
return null;
}
try {
return $this->recordFactory->createResolvedRecordFromDatabaseRow('pages', $rawRecord);
} catch (\Exception $e) {
// @todo: Catching \Exception here is a code smell, this shouldn't be so generic and can hide away too many issues.
$this->logger->error(
'Failed to create page record instance for breadcrumb',
['pid' => $pid, 'exception' => $e->getMessage()]
);
return null;
}
}
private function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}