TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
<?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\Tstemplate\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\VisibilityAspect;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Abstract class with helper methods for single 3rd level Template module controllers.
|
||||
*
|
||||
* @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
abstract class AbstractTemplateModuleController
|
||||
{
|
||||
protected IconFactory $iconFactory;
|
||||
protected UriBuilder $uriBuilder;
|
||||
protected ConnectionPool $connectionPool;
|
||||
protected SiteFinder $siteFinder;
|
||||
protected ComponentFactory $componentFactory;
|
||||
private DataHandler $dataHandler;
|
||||
private TcaSchemaFactory $tcaSchemaFactory;
|
||||
|
||||
public function injectIconFactory(IconFactory $iconFactory): void
|
||||
{
|
||||
$this->iconFactory = $iconFactory;
|
||||
}
|
||||
|
||||
public function injectUriBuilder(UriBuilder $uriBuilder)
|
||||
{
|
||||
$this->uriBuilder = $uriBuilder;
|
||||
}
|
||||
|
||||
public function injectConnectionPool(ConnectionPool $connectionPool)
|
||||
{
|
||||
$this->connectionPool = $connectionPool;
|
||||
}
|
||||
|
||||
public function injectDataHandler(DataHandler $dataHandler)
|
||||
{
|
||||
$this->dataHandler = $dataHandler;
|
||||
}
|
||||
|
||||
public function injectSiteFinder(SiteFinder $siteFinder)
|
||||
{
|
||||
$this->siteFinder = $siteFinder;
|
||||
}
|
||||
|
||||
public function injectTcaSchemaFactory(TcaSchemaFactory $tcaSchemaFactory)
|
||||
{
|
||||
$this->tcaSchemaFactory = $tcaSchemaFactory;
|
||||
}
|
||||
|
||||
public function injectComponentFactory(ComponentFactory $componentFactory): void
|
||||
{
|
||||
$this->componentFactory = $componentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action shared by info/modify ond constant editor to create a new "extension template"
|
||||
*/
|
||||
protected function createExtensionTemplateAction(ServerRequestInterface $request, string $redirectTarget): ResponseInterface
|
||||
{
|
||||
$pageUid = (int)($request->getQueryParams()['id'] ?? 0);
|
||||
if ($pageUid === 0) {
|
||||
throw new \RuntimeException('No proper page uid given', 1661333864);
|
||||
}
|
||||
$recordData['sys_template']['NEW'] = [
|
||||
'pid' => $pageUid,
|
||||
'title' => '+ext',
|
||||
];
|
||||
$this->dataHandler->start($recordData, []);
|
||||
$this->dataHandler->process_datamap();
|
||||
return new RedirectResponse($this->uriBuilder->buildUriFromRoute($redirectTarget, ['id' => $pageUid]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action shared by info/modify ond constant editor to create a new "site template"
|
||||
*/
|
||||
protected function createNewWebsiteTemplateAction(ServerRequestInterface $request, string $redirectTarget): ResponseInterface
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$pageUid = (int)($request->getQueryParams()['id'] ?? 0);
|
||||
if ($pageUid === 0) {
|
||||
throw new \RuntimeException('No proper page uid given', 1661333863);
|
||||
}
|
||||
$recordData['sys_template']['NEW'] = [
|
||||
'pid' => $pageUid,
|
||||
'title' => $languageService->sL('LLL:EXT:tstemplate/Resources/Private/Language/locallang.xlf:noRecordFound.createRootTypoScriptRecord.title.placeholder'),
|
||||
'sorting' => 0,
|
||||
'root' => 1,
|
||||
'clear' => 3,
|
||||
'config' => "\n"
|
||||
. "# Default PAGE object:\n"
|
||||
. "page = PAGE\n"
|
||||
. "page.10 = TEXT\n"
|
||||
. "page.10.value = HELLO WORLD!\n",
|
||||
];
|
||||
$this->dataHandler->start($recordData, []);
|
||||
$this->dataHandler->process_datamap();
|
||||
return new RedirectResponse($this->uriBuilder->buildUriFromRoute($redirectTarget, ['id' => $pageUid]));
|
||||
}
|
||||
|
||||
protected function addPreviewButtonToDocHeader(ModuleTemplate $view, array $pageRecord): void
|
||||
{
|
||||
$previewUriBuilder = PreviewUriBuilder::create($pageRecord);
|
||||
if ($previewUriBuilder->isPreviewable()) {
|
||||
$view->addButtonToButtonBar($this->componentFactory->createViewButton(
|
||||
$previewUriBuilder
|
||||
->withRootLine(BackendUtility::BEgetRootLine($pageRecord['uid']))
|
||||
->buildDispatcherDataAttributes() ?? []
|
||||
), ButtonBar::BUTTON_POSITION_LEFT, 99);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addShortcutButtonToDocHeader(ModuleTemplate $view, string $moduleIdentifier, array $pageInfo, int $pageUid, string $moduleTitle): void
|
||||
{
|
||||
$shortcutTitle = sprintf(
|
||||
'%s: %s [%d]',
|
||||
$moduleTitle,
|
||||
BackendUtility::getRecordTitle('pages', $pageInfo),
|
||||
$pageUid
|
||||
);
|
||||
$view->getDocHeaderComponent()->setShortcutContext(
|
||||
$moduleIdentifier,
|
||||
$shortcutTitle,
|
||||
['id' => $pageUid]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest page row that has a template up in rootline
|
||||
*/
|
||||
protected function getClosestAncestorPageWithTemplateRecord(int $pageId): array
|
||||
{
|
||||
$rootLine = BackendUtility::BEgetRootLine($pageId);
|
||||
foreach ($rootLine as $rootlineNode) {
|
||||
if ($this->getFirstTemplateRecordOnPage((int)$rootlineNode['uid'])) {
|
||||
return $rootlineNode;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getScopedRootline(SiteInterface $site, array $fullRootLine): array
|
||||
{
|
||||
if (!$site instanceof Site) {
|
||||
return $fullRootLine;
|
||||
}
|
||||
if (!$site->isTypoScriptRoot()) {
|
||||
return $fullRootLine;
|
||||
}
|
||||
$rootLineUntilSite = [];
|
||||
foreach ($fullRootLine as $index => $rootlinePage) {
|
||||
$rootlinePageId = (int)($rootlinePage['uid'] ?? 0);
|
||||
$rootLineUntilSite[$index] = $rootlinePage;
|
||||
if ($rootlinePageId === $site->getRootPageId()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $rootLineUntilSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all template records on a page.
|
||||
*/
|
||||
protected function getAllTemplateRecordsOnPage(int $pageId): array
|
||||
{
|
||||
if (!$pageId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$templateRecords = [];
|
||||
|
||||
try {
|
||||
$site = $this->siteFinder->getSiteByRootPageId($pageId);
|
||||
if ($site->isTypoScriptRoot()) {
|
||||
$typoScript = $site->getTypoScript();
|
||||
$templateRecords[] = [
|
||||
'type' => 'site',
|
||||
'pid' => $pageId,
|
||||
'constants' => $typoScript->constants ?? '',
|
||||
'config' => $typoScript->setup ?? '',
|
||||
'root' => 1,
|
||||
'clear' => 1,
|
||||
'sorting' => -1,
|
||||
'uid' => -1,
|
||||
'site' => $site,
|
||||
'title' => $site->getConfiguration()['websiteTitle'] ?? '',
|
||||
];
|
||||
}
|
||||
} catch (SiteNotFoundException) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
$result = $this->getTemplateQueryBuilder($pageId)->executeQuery();
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
$templateRecords[] = [...$row, 'type' => 'sys_template'];
|
||||
}
|
||||
return $templateRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single sys_template record attached to a single page.
|
||||
* If multiple template records are on this page, the first (order by sorting)
|
||||
* record will be returned, unless a specific template uid is specified via $templateUid
|
||||
*
|
||||
* @param int $pageId The pid to select sys_template records from
|
||||
* @param int $templateUid Optional template uid
|
||||
* @return array<string,mixed>|false Returns the template record or false if none was found
|
||||
*/
|
||||
protected function getFirstTemplateRecordOnPage(int $pageId, int $templateUid = 0): array|false
|
||||
{
|
||||
if (empty($pageId)) {
|
||||
return false;
|
||||
}
|
||||
$queryBuilder = $this->getTemplateQueryBuilder($pageId)->setMaxResults(1);
|
||||
if ($templateUid) {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($templateUid, Connection::PARAM_INT))
|
||||
);
|
||||
}
|
||||
return $queryBuilder->executeQuery()->fetchAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prepare the query builder for getting sys_template records from a given pid.
|
||||
*/
|
||||
protected function getTemplateQueryBuilder(int $pid): QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_template');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$queryBuilder->select('*')
|
||||
->from('sys_template')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT))
|
||||
);
|
||||
|
||||
$schema = $this->tcaSchemaFactory->has('sys_template')
|
||||
? $this->tcaSchemaFactory->get('sys_template')
|
||||
: null;
|
||||
if ($schema && $schema->hasCapability(TcaSchemaCapability::SortByField)) {
|
||||
$queryBuilder
|
||||
->orderBy($schema->getCapability(TcaSchemaCapability::SortByField)->getFieldName());
|
||||
}
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a VisibilityAspect that simulates frontend-like behavior:
|
||||
* hidden templates and templates outside their scheduled time window
|
||||
* are excluded, as they would be in the frontend.
|
||||
*/
|
||||
protected function createVisibilityAspect(): VisibilityAspect
|
||||
{
|
||||
// For the context of the TypoScript management backend, we want to
|
||||
// edit TypoScript records that are hidden. But in a backend submodule like
|
||||
// the ActiveTypoScriptController / TemplateAnalyzerController, only
|
||||
// non-hidden records with matching time constraints should be evaluated,
|
||||
// just like in the frontend.
|
||||
return new VisibilityAspect(
|
||||
includeHiddenPages: true,
|
||||
includeHiddenContent: false,
|
||||
includeDeletedRecords: false,
|
||||
includeScheduledRecords: false,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user