TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
<?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\Service;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Backend\Domain\Model\Language\LanguageItem;
|
||||
use TYPO3\CMS\Backend\Domain\Model\Language\LanguageStatus;
|
||||
use TYPO3\CMS\Backend\Domain\Model\Language\PageLanguageInformation;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Site\Entity\NullSite;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* Central service for gathering page language information.
|
||||
*
|
||||
* This service provides the SINGLE source of truth for:
|
||||
* - Which languages exist (have translations)
|
||||
* - Which languages can be created (permissions + missing)
|
||||
* - Which languages are unavailable (no permission)
|
||||
* - Translation records with workspace overlay
|
||||
* - UI properties (labels, flags, icons)
|
||||
*
|
||||
* Note: Languages marked as "Unavailable" are included in the result
|
||||
* for potential future use (e.g., showing "No Access" indicators),
|
||||
* but controllers currently filter them out before displaying the UI.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class PageLanguageInformationService
|
||||
{
|
||||
public function __construct(
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
private ConnectionPool $connectionPool,
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
private FrontendInterface $runtimeCache,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get complete language information for a page.
|
||||
*
|
||||
* This method fetches and analyzes all language-related data for a page,
|
||||
* including existing translations, permissions, and available languages.
|
||||
*/
|
||||
public function getLanguageInformationForPage(
|
||||
int $pageId,
|
||||
SiteInterface $site,
|
||||
BackendUserAuthentication $backendUser
|
||||
): PageLanguageInformation {
|
||||
// Build cache key: page ID + workspace + user ID (for permissions)
|
||||
$cacheKey = $this->getCacheKeyForPage($pageId, $backendUser);
|
||||
|
||||
// Check runtime cache
|
||||
$cachedInfo = $this->runtimeCache->get($cacheKey);
|
||||
if ($cachedInfo instanceof PageLanguageInformation) {
|
||||
return $cachedInfo;
|
||||
}
|
||||
|
||||
// Get ALL enabled languages (without permission filtering)
|
||||
// We need to do permission checks ourselves to properly classify as Unavailable
|
||||
if ($site instanceof Site) {
|
||||
// For Site we use getAllLanguages(), because getLanguages() would remove "disabled" languages
|
||||
$allLanguages = $site->getAllLanguages();
|
||||
} else {
|
||||
$allLanguages = $site->getLanguages();
|
||||
}
|
||||
|
||||
// For NullSite (page 0), respect mod.SHARED.disableLanguages TSconfig
|
||||
$disabledLanguages = [];
|
||||
if ($site instanceof NullSite) {
|
||||
$pageTs = BackendUtility::getPagesTSconfig($pageId);
|
||||
$pageTs = $pageTs['mod.']['SHARED.'] ?? [];
|
||||
$disabledLanguages = GeneralUtility::intExplode(',', (string)($pageTs['disableLanguages'] ?? ''), true);
|
||||
}
|
||||
|
||||
$existingTranslations = $this->fetchExistingTranslations($pageId, $backendUser);
|
||||
$canUserCreateTranslations = $this->canUserCreatePageTranslations($backendUser, $pageId);
|
||||
|
||||
$availableLanguages = [];
|
||||
$languageStatuses = [];
|
||||
$creatableLanguageIds = [];
|
||||
foreach ($allLanguages as $siteLanguage) {
|
||||
$languageId = $siteLanguage->getLanguageId();
|
||||
|
||||
if (in_array($languageId, $disabledLanguages, true)) {
|
||||
// For NullSite: skip languages disabled via TSconfig
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if user has access to this specific language
|
||||
if (!$backendUser->checkLanguageAccess($languageId)) {
|
||||
// Language exists in site config but user has no access
|
||||
$languageStatuses[$languageId] = LanguageStatus::Unavailable;
|
||||
// Include in availableLanguages for potential future use (e.g., "No Access" indicators)
|
||||
// Note: Controllers currently filter out Unavailable languages before display
|
||||
$availableLanguages[$languageId] = $siteLanguage;
|
||||
continue;
|
||||
}
|
||||
|
||||
// User has access to this language
|
||||
$availableLanguages[$languageId] = $siteLanguage;
|
||||
|
||||
if ($languageId === 0 || isset($existingTranslations[$languageId])) {
|
||||
// Default language or existing translation
|
||||
$languageStatuses[$languageId] = LanguageStatus::Existing;
|
||||
} elseif ($canUserCreateTranslations) {
|
||||
// Can be created (user has page edit permission AND language access)
|
||||
$languageStatuses[$languageId] = LanguageStatus::Creatable;
|
||||
$creatableLanguageIds[] = $languageId;
|
||||
} else {
|
||||
// Cannot be created (no page edit permission, but has language access)
|
||||
$languageStatuses[$languageId] = LanguageStatus::Unavailable;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter existingTranslations to only include languages that are available.
|
||||
// This ensures consistency: a language with a translation in the database but whose
|
||||
// language does not exist in site configuration should not appear as "existing" in the UI.
|
||||
$existingTranslations = array_intersect_key($existingTranslations, $availableLanguages);
|
||||
|
||||
$languageInformation = new PageLanguageInformation(
|
||||
pageId: $pageId,
|
||||
availableLanguages: $availableLanguages,
|
||||
languageStatuses: $languageStatuses,
|
||||
existingTranslations: $existingTranslations,
|
||||
creatableLanguageIds: $creatableLanguageIds,
|
||||
canUserCreateTranslations: $canUserCreateTranslations,
|
||||
languageItems: $this->getLanguageItems($languageStatuses, $availableLanguages),
|
||||
);
|
||||
|
||||
// Store in runtime cache for this request
|
||||
$this->runtimeCache->set($cacheKey, $languageInformation);
|
||||
|
||||
return $languageInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language items ready for UI rendering. Includes all UI properties (labels, flags, etc.)
|
||||
*
|
||||
* @return LanguageItem[] Array of language items
|
||||
*/
|
||||
private function getLanguageItems(array $languageStatuses, array $availableLanguages): array
|
||||
{
|
||||
$languageItems = [];
|
||||
foreach ($availableLanguages as $siteLanguage) {
|
||||
$languageId = $siteLanguage->getLanguageId();
|
||||
$languageItems[] = new LanguageItem(
|
||||
siteLanguage: $siteLanguage,
|
||||
status: $languageStatuses[$languageId] ?? LanguageStatus::Unavailable,
|
||||
);
|
||||
}
|
||||
return $languageItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch existing page translations with workspace overlay.
|
||||
*
|
||||
* This is the centralized logic that was previously duplicated across
|
||||
* PageLayoutController and RecordListController.
|
||||
*
|
||||
* @param int $pageId Page ID
|
||||
* @param BackendUserAuthentication $backendUser Current backend user
|
||||
* @return array<int, array> Translation records keyed by language ID
|
||||
*/
|
||||
private function fetchExistingTranslations(
|
||||
int $pageId,
|
||||
BackendUserAuthentication $backendUser
|
||||
): array {
|
||||
$schema = $this->tcaSchemaFactory->get('pages');
|
||||
$languageCapability = $schema->getCapability(TcaSchemaCapability::Language);
|
||||
$languageField = $languageCapability->getLanguageField()->getName();
|
||||
$translationOriginField = $languageCapability->getTranslationOriginPointerField()->getName();
|
||||
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()
|
||||
->add(new DeletedRestriction())
|
||||
->add(new WorkspaceRestriction($backendUser->workspace));
|
||||
|
||||
$queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
$translationOriginField,
|
||||
$queryBuilder->createNamedParameter($pageId, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->neq(
|
||||
$languageField,
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
$existingTranslations = [];
|
||||
$statement = $queryBuilder->executeQuery();
|
||||
|
||||
while ($row = $statement->fetchAssociative()) {
|
||||
BackendUtility::workspaceOL('pages', $row, $backendUser->workspace);
|
||||
if ($row && VersionState::tryFrom($row['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) {
|
||||
$existingTranslations[(int)$row[$languageField]] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $existingTranslations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can create page translations.
|
||||
*
|
||||
* @param BackendUserAuthentication $backendUser Current backend user
|
||||
* @param int $pageId Page ID
|
||||
* @return bool True if user can create translations
|
||||
*/
|
||||
private function canUserCreatePageTranslations(
|
||||
BackendUserAuthentication $backendUser,
|
||||
int $pageId
|
||||
): bool {
|
||||
// Check table permission
|
||||
if (!$backendUser->check('tables_modify', 'pages')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check page permission
|
||||
$pageInfo = BackendUtility::readPageAccess($pageId, $backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if (!$pageInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $backendUser->doesUserHaveAccess($pageInfo, Permission::PAGE_EDIT);
|
||||
}
|
||||
|
||||
private function getCacheKeyForPage(int $pageId, BackendUserAuthentication $backendUser): string
|
||||
{
|
||||
return 'PageLanguageInformation_' . $pageId . '_' . $backendUser->workspace . '_' . $backendUser->getUserId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?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\Service;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Domain\RecordFactory;
|
||||
use TYPO3\CMS\Core\Domain\RecordInterface;
|
||||
use TYPO3\CMS\Core\LinkHandling\PageTypeLinkResolver;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* @internal To be used in the Content > Layout module and not part of public API.
|
||||
*/
|
||||
readonly class PageLinkMessageProvider
|
||||
{
|
||||
public function __construct(
|
||||
protected ConnectionPool $connectionPool,
|
||||
protected PageTypeLinkResolver $pageTypeLinkResolver,
|
||||
protected RecordFactory $recordFactory,
|
||||
protected UriBuilder $uriBuilder,
|
||||
protected TcaSchemaFactory $tcaSchemaFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generates the messages that are displayed in the Content > Layout module
|
||||
* to show the target of a page of type link.
|
||||
*
|
||||
* @param array $pageRecord the database row of the page of type link
|
||||
* @return array{
|
||||
* message: string,
|
||||
* state: ContextualFeedbackSeverity
|
||||
* }
|
||||
*/
|
||||
public function generateMessagesForPageTypeLink(array $pageRecord, ServerRequestInterface $request): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$linkInfo = $this->pageTypeLinkResolver->resolveTypolinkParts($pageRecord);
|
||||
if (($linkInfo['url'] ?? '') === '') {
|
||||
// No link is set
|
||||
return $this->getMessageForMissingLink($request, $languageService);
|
||||
}
|
||||
return match (($linkInfo['type'] ?? '')) {
|
||||
// The current page links to another page
|
||||
'page' => $this->getMessageForPageTypeLinkToPage($linkInfo, $request, $languageService),
|
||||
// The current page links to another record
|
||||
'record' => $this->getMessageForPageTypeLinkToRecord($linkInfo, $request, $languageService),
|
||||
default => $this->getMessageBasedOnPageRecordAndUnhandledLinkTypes($pageRecord, $linkInfo, $request, $languageService),
|
||||
};
|
||||
}
|
||||
|
||||
protected function generateFrontendLink(string $pageLink, LanguageService $languageService, string $type): array
|
||||
{
|
||||
$pageLink = htmlspecialchars($pageLink);
|
||||
$pageLinkHtml = sprintf('<a href="%s" target="_blank" rel="noreferrer">%s</a>', $pageLink, $pageLink);
|
||||
$linkTypeLabel = $languageService->translate($type, 'backend.links') ?? $languageService->translate('destination', 'backend.links');
|
||||
return [
|
||||
$pageLinkHtml,
|
||||
$linkTypeLabel,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a link to the page module containing the content element and
|
||||
* displays information about the content element.
|
||||
*/
|
||||
protected function generateLinkToContentElement(int $fragment, ServerRequestInterface $request, string $linkedPage): string
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$record = $this->findRecord('tt_content', $fragment);
|
||||
if ($record === null) {
|
||||
return '';
|
||||
}
|
||||
$params = [
|
||||
'edit' => [$record->getMainType() => [$record->getUid() => 'edit']],
|
||||
'module' => '',
|
||||
'returnUrl' => (string)$request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
];
|
||||
$uri = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
|
||||
$recordLabel = htmlspecialchars($record->get('header'));
|
||||
|
||||
if (empty($recordLabel)) {
|
||||
$recordLabel = '<em>[' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>';
|
||||
}
|
||||
$linkToContentElement = sprintf('<a href="%s">%s</a>', htmlspecialchars($uri), $recordLabel);
|
||||
return sprintf(
|
||||
$languageService->translate('link_destination_content_element', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
$linkToContentElement,
|
||||
$linkedPage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* message: string,
|
||||
* state: ContextualFeedbackSeverity,
|
||||
* }
|
||||
*/
|
||||
protected function getMessageForMissingLink(ServerRequestInterface $request, LanguageService $languageService): array
|
||||
{
|
||||
// No link is set
|
||||
return [
|
||||
'message' => sprintf(
|
||||
$languageService->translate('link_missing', 'backend.pages.messages'),
|
||||
$languageService->translate('link', 'core.db.pages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
),
|
||||
'state' => ContextualFeedbackSeverity::ERROR,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $linkInfo
|
||||
* @return array{
|
||||
* message: string,
|
||||
* state: ContextualFeedbackSeverity,
|
||||
* }
|
||||
*/
|
||||
protected function getMessageForPageTypeLinkToPage(
|
||||
array $linkInfo,
|
||||
ServerRequestInterface $request,
|
||||
LanguageService $languageService,
|
||||
): array {
|
||||
$backendUser = $this->getBackendUser();
|
||||
if (($linkInfo['pageuid'] ?? '') === 'current') {
|
||||
return [
|
||||
'message' => $this->generateLinkToCurrent($linkInfo, $request, $languageService),
|
||||
'state' => ContextualFeedbackSeverity::INFO,
|
||||
];
|
||||
}
|
||||
$linkToPid = $this->uriBuilder->buildUriFromRoute('web_layout', ['id' => $linkInfo['pageuid']]);
|
||||
$path = BackendUtility::getRecordPath($linkInfo['pageuid'], $backendUser->getPagePermsClause(Permission::PAGE_SHOW), 1000);
|
||||
$linkedPath = '<a href="' . htmlspecialchars((string)$linkToPid) . '">' . htmlspecialchars($path) . '</a>';
|
||||
if (MathUtility::canBeInterpretedAsInteger($linkInfo['fragment'] ?? false)) {
|
||||
return [
|
||||
'message' => $this->generateLinkToContentElement((int)$linkInfo['fragment'], $request, $linkedPath),
|
||||
'state' => ContextualFeedbackSeverity::INFO,
|
||||
];
|
||||
}
|
||||
$message = sprintf(
|
||||
$languageService->translate('link_destination', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
$languageService->translate('page', 'backend.links'),
|
||||
$linkedPath
|
||||
);
|
||||
if ($linkInfo['additionalParams'] ?? false) {
|
||||
$message .= ' ' . sprintf(
|
||||
$languageService->translate('link_destination_additional_parameters', 'backend.pages.messages'),
|
||||
htmlspecialchars($linkInfo['additionalParams']),
|
||||
);
|
||||
}
|
||||
return [
|
||||
'message' => $message,
|
||||
'state' => ContextualFeedbackSeverity::INFO,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the backend message for link pages linking to current request page,
|
||||
* with or without appending parameters.
|
||||
*/
|
||||
protected function generateLinkToCurrent(array $linkInfo, ServerRequestInterface $request, LanguageService $languageService): string
|
||||
{
|
||||
$queryParameters = $linkInfo['url'] ?? '';
|
||||
if ($queryParameters === '') {
|
||||
return sprintf(
|
||||
$languageService->translate('link_current', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
$languageService->translate('link_current_with_added_queryparameters', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
$queryParameters,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $pageRecord the database row of the page of type link
|
||||
* @param array $linkInfo
|
||||
* @return array{
|
||||
* message: string,
|
||||
* state: ContextualFeedbackSeverity,
|
||||
* }
|
||||
*/
|
||||
protected function getMessageBasedOnPageRecordAndUnhandledLinkTypes(
|
||||
array $pageRecord,
|
||||
array $linkInfo,
|
||||
ServerRequestInterface $request,
|
||||
LanguageService $languageService,
|
||||
): array {
|
||||
$pageLink = $this->pageTypeLinkResolver->resolvePageLinkUrl($pageRecord, $request);
|
||||
if ($pageLink === '') {
|
||||
// The link cannot be resolved
|
||||
return [
|
||||
'message' => sprintf(
|
||||
$languageService->translate('link_invalid', 'backend.pages.messages'),
|
||||
$languageService->translate('link', 'core.db.pages'),
|
||||
htmlspecialchars($pageRecord['link'] ?? ''),
|
||||
),
|
||||
'state' => ContextualFeedbackSeverity::ERROR,
|
||||
];
|
||||
}
|
||||
// Display the frontend link destination, for example external URL or link to a file or email address
|
||||
[$pageLinkHtml, $linkTypeLabel] = $this->generateFrontendLink($pageLink, $languageService, $linkInfo['type']);
|
||||
return [
|
||||
'message' => sprintf(
|
||||
$languageService->translate('link_destination', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
$linkTypeLabel,
|
||||
$pageLinkHtml
|
||||
),
|
||||
'state' => ContextualFeedbackSeverity::INFO,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $linkInfo
|
||||
* @return array{
|
||||
* message: string,
|
||||
* state: ContextualFeedbackSeverity,
|
||||
* }
|
||||
*/
|
||||
protected function getMessageForPageTypeLinkToRecord(
|
||||
array $linkInfo,
|
||||
ServerRequestInterface $request,
|
||||
LanguageService $languageService,
|
||||
): array {
|
||||
$pageTsConfig = BackendUtility::getPagesTSconfig($request->getQueryParams()['id'] ?? 0);
|
||||
$linkConfig = $pageTsConfig['TCEMAIN.']['linkHandler.'][$linkInfo['identifier'] . '.'] ?? [];
|
||||
$table = $linkConfig['configuration.']['table'] ?? null;
|
||||
if ($linkConfig === [] || $table === null) {
|
||||
return [
|
||||
'message' => 'No page TSconfig definition found for link of type ' . $linkInfo['identifier'] . '. Expected TCEMAIN.linkHandler.' . $linkInfo['identifier'],
|
||||
'state' => ContextualFeedbackSeverity::ERROR,
|
||||
];
|
||||
}
|
||||
$uid = (int)$linkInfo['uid'];
|
||||
$record = $this->findRecord($table, $uid);
|
||||
if ($record === null) {
|
||||
return [
|
||||
'message' => 'This page links to record with uid ' . $uid . ' in table ' . $table . ' which could not be resolved.',
|
||||
'state' => ContextualFeedbackSeverity::ERROR,
|
||||
];
|
||||
}
|
||||
$params = [
|
||||
'edit' => [$table => [$uid => 'edit']],
|
||||
'module' => 'records',
|
||||
'returnUrl' => '',
|
||||
];
|
||||
$linkToPid = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
$label = BackendUtility::getRecordTitle($table, $record);
|
||||
$label = BackendUtility::cropToTitleLength($label);
|
||||
$linkedPath = '<a href="' . htmlspecialchars($linkToPid) . '">' . htmlspecialchars($label) . '</a>';
|
||||
$message = sprintf(
|
||||
$languageService->translate('link_destination_record', 'backend.pages.messages'),
|
||||
$languageService->translate('doktype.link', 'core.db.pages'),
|
||||
$languageService->sL($schema->getTitle()),
|
||||
$linkedPath,
|
||||
);
|
||||
if ($linkInfo['additionalParams'] ?? false) {
|
||||
$message .= ' ' . sprintf(
|
||||
$languageService->translate('link_destination_additional_parameters', 'backend.pages.messages'),
|
||||
htmlspecialchars($linkInfo['additionalParams']),
|
||||
);
|
||||
}
|
||||
return [
|
||||
'message' => $message,
|
||||
'state' => ContextualFeedbackSeverity::INFO,
|
||||
];
|
||||
}
|
||||
|
||||
protected function findRecord(string $table, int $uid): ?RecordInterface
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
||||
$queryBuilder
|
||||
->select('*')
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
|
||||
)
|
||||
);
|
||||
$row = $queryBuilder->executeQuery()->fetchAssociative();
|
||||
if ($row === false) {
|
||||
return null;
|
||||
}
|
||||
return $this->recordFactory->createFromDatabaseRow($table, $row);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user