TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<?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\Controller\Page;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\PageRendererBackendSetupTrait;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryHelper;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The "move page" wizard. Reachable via records module "Move page" on page records.
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
final readonly class MovePageController
|
||||
{
|
||||
use PageRendererBackendSetupTrait;
|
||||
|
||||
public function __construct(
|
||||
private PageRenderer $pageRenderer,
|
||||
private BackendViewFactory $backendViewFactory,
|
||||
private UriBuilder $uriBuilder,
|
||||
private LanguageServiceFactory $languageServiceFactory,
|
||||
private ExtensionConfiguration $extensionConfiguration,
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->setUpBasicPageRendererForBackend(
|
||||
$this->pageRenderer,
|
||||
$this->extensionConfiguration,
|
||||
$request,
|
||||
$this->languageServiceFactory->createFromUserPreferences($this->getBackendUser())
|
||||
);
|
||||
$view = $this->backendViewFactory->create($request);
|
||||
$queryParams = $request->getQueryParams();
|
||||
$contentOnly = $queryParams['contentOnly'] ?? false;
|
||||
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/tree/page-browser.js');
|
||||
$this->pageRenderer->getJavaScriptRenderer()->addJavaScriptModuleInstruction(
|
||||
JavaScriptModuleInstruction::create('@typo3/backend/wizard/move-page.js', 'MovePage')->instance()
|
||||
);
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/Wizards/move_page.xlf');
|
||||
|
||||
$targetPid = (int)($queryParams['expandPage'] ?? 0);
|
||||
$pageIdToMove = (int)($queryParams['uid'] ?? 0);
|
||||
$makeCopy = (bool)($queryParams['makeCopy'] ?? 0);
|
||||
|
||||
if ($targetPid) {
|
||||
$view->assignMultiple($this->getContentVariables($pageIdToMove, $targetPid));
|
||||
}
|
||||
$view->assignMultiple([
|
||||
'activePage' => $targetPid,
|
||||
'contentOnly' => $contentOnly,
|
||||
// Make-copy checkbox (clicking this will reload the page with the GET var makeCopy set differently):
|
||||
'makeCopyChecked' => $makeCopy,
|
||||
'makeCopyUrl' => $this->uriBuilder->buildUriFromRoute(
|
||||
'move_page',
|
||||
[
|
||||
'uid' => $pageIdToMove,
|
||||
'makeCopy' => !$makeCopy,
|
||||
]
|
||||
),
|
||||
]);
|
||||
|
||||
$content = $view->render('Page/MovePage');
|
||||
if ($contentOnly) {
|
||||
return new HtmlResponse($content);
|
||||
}
|
||||
$this->pageRenderer->setBodyContent('<body>' . $content);
|
||||
return new HtmlResponse($this->pageRenderer->render($request));
|
||||
}
|
||||
|
||||
private function getContentVariables(int $pageIdToMove, int $targetPid): array
|
||||
{
|
||||
$elementRow = BackendUtility::getRecordWSOL('pages', $pageIdToMove);
|
||||
$targetRow = BackendUtility::getRecordWSOL('pages', $targetPid);
|
||||
if (!$this->getBackendUser()->doesUserHaveAccess($targetRow, Permission::PAGE_EDIT)) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'targetHasSubpages' => $this->pageHasSubpages($targetPid),
|
||||
'element' => [
|
||||
'record' => $elementRow,
|
||||
'recordTooltip' => BackendUtility::getRecordIconAltText($elementRow, 'pages', false),
|
||||
'recordTitle' => BackendUtility::getRecordTitle('pages', $elementRow),
|
||||
'recordPath' => BackendUtility::getRecordPath($pageIdToMove, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 0),
|
||||
],
|
||||
'target' => [
|
||||
'record' => $targetRow,
|
||||
'recordTooltip' => BackendUtility::getRecordIconAltText($targetRow, 'pages', false),
|
||||
'recordTitle' => BackendUtility::getRecordTitle('pages', $targetRow),
|
||||
'recordPath' => BackendUtility::getRecordPath($targetPid, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 0),
|
||||
],
|
||||
'positions' => [
|
||||
'above' => $this->getTargetForAboveInsert($targetRow),
|
||||
'inside' => $targetRow['uid'],
|
||||
'below' => $targetRow['uid'] * -1,
|
||||
],
|
||||
'hasEditPermissions' => $this->getBackendUser()->doesUserHaveAccess($elementRow, Permission::PAGE_EDIT),
|
||||
'isDifferentPage' => $pageIdToMove !== $targetRow['uid'],
|
||||
];
|
||||
}
|
||||
|
||||
private function getTargetForAboveInsert(array $targetRow): int
|
||||
{
|
||||
$targetPageId = (int)$targetRow['uid'];
|
||||
$subpages = $this->getSubpagesForPageId($targetRow['pid']);
|
||||
if (in_array($targetPageId, $subpages, true)) {
|
||||
// Set pointer in array to $targetPid
|
||||
while (current($subpages) !== $targetPageId) {
|
||||
if (next($subpages) === false) {
|
||||
// We reached the end of the array and couldn't find the target pid (how?). Fall back to pid
|
||||
return (int)$targetRow['pid'];
|
||||
}
|
||||
}
|
||||
$previousItem = prev($subpages);
|
||||
if ($previousItem !== false) {
|
||||
return $previousItem * -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)$targetRow['pid'];
|
||||
}
|
||||
|
||||
private function getSubpagesForPageId(int $pageId): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder
|
||||
->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
||||
|
||||
return $queryBuilder
|
||||
->select('uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId)),
|
||||
$queryBuilder->expr()->or(
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter(\Local\Multilanguage\Service\DefaultLanguageTagService::getTag())),
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter('')),
|
||||
),
|
||||
QueryHelper::stripLogicalOperatorPrefix(
|
||||
$this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
)
|
||||
)
|
||||
->orderBy('sorting')
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
}
|
||||
|
||||
private function pageHasSubpages(int $pageId): bool
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder
|
||||
->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
||||
|
||||
$count = (int)$queryBuilder
|
||||
->count('uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId)),
|
||||
$queryBuilder->expr()->or(
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter(\Local\Multilanguage\Service\DefaultLanguageTagService::getTag())),
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter('')),
|
||||
),
|
||||
QueryHelper::stripLogicalOperatorPrefix(
|
||||
$this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
private function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?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\Controller\Page;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEffectivePid;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowInitializeNew;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUniqueUidNewRow;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\UserTsConfig;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
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\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* "Create multiple pages" controller
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class NewMultiplePagesController
|
||||
{
|
||||
public function __construct(
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected TcaSchemaFactory $tcaSchemaFactory,
|
||||
protected ComponentFactory $componentFactory,
|
||||
protected FormDataCompiler $formDataCompiler,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Main function Handling input variables and rendering main view.
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pageUid = (int)$request->getQueryParams()['id'];
|
||||
|
||||
// Show only if there is a valid page and if this page may be viewed by the user
|
||||
$pageRecord = BackendUtility::readPageAccess($pageUid, $backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if (!is_array($pageRecord)) {
|
||||
// User has no permission on parent page, should not happen, just render an empty page
|
||||
return $view->renderResponse('Dummy/Index');
|
||||
}
|
||||
|
||||
// Doc header handling
|
||||
$view->getDocHeaderComponent()->setPageBreadcrumb($pageRecord);
|
||||
$view->addButtonToButtonBar(
|
||||
$this->componentFactory->createViewButton(
|
||||
PreviewUriBuilder::create($pageRecord)
|
||||
->withRootLine(BackendUtility::BEgetRootLine($pageUid))
|
||||
->buildDispatcherDataAttributes() ?? []
|
||||
)
|
||||
);
|
||||
|
||||
$calculatedPermissions = new Permission($backendUser->calcPerms($pageRecord));
|
||||
$canCreateNew = $backendUser->isAdmin() || $calculatedPermissions->createPagePermissionIsGranted();
|
||||
|
||||
$view->assignMultiple([
|
||||
'canCreateNew' => $canCreateNew,
|
||||
'maxTitleLength' => $backendUser->uc['titleLen'] ?? 20,
|
||||
'pageUid' => $pageUid,
|
||||
]);
|
||||
|
||||
if ($canCreateNew) {
|
||||
$newPagesData = (array)($request->getParsedBody()['pages'] ?? []);
|
||||
if (!empty($newPagesData)) {
|
||||
$hasNewPagesData = true;
|
||||
$afterExisting = isset($request->getParsedBody()['createInListEnd']);
|
||||
$hidePages = isset($request->getParsedBody()['hidePages']);
|
||||
$hidePagesInMenu = isset($request->getParsedBody()['hidePagesInMenus']);
|
||||
$pagesCreated = $this->createPages($newPagesData, $pageUid, $afterExisting, $hidePages, $hidePagesInMenu);
|
||||
$view->assign('pagesCreated', $pagesCreated);
|
||||
$subPages = $this->getSubPagesOfPage($pageUid);
|
||||
$visiblePages = [];
|
||||
foreach ($subPages as $page) {
|
||||
$calculatedPermissions = new Permission($backendUser->calcPerms($page));
|
||||
if ($backendUser->isAdmin() || $calculatedPermissions->showPagePermissionIsGranted()) {
|
||||
$visiblePages[] = $page;
|
||||
}
|
||||
}
|
||||
$view->assign('visiblePages', $visiblePages);
|
||||
} else {
|
||||
$hasNewPagesData = false;
|
||||
$types = $this->getTypeSelectData($pageUid, $request);
|
||||
$filteredTypes = [];
|
||||
$types = $this->filterTypesThatOnlyRequireTitle($types, $filteredTypes);
|
||||
$view->assign('pageTypes', $types);
|
||||
$view->assign('filteredTypes', $filteredTypes);
|
||||
$view->assign('wizardConfiguration', ['positionData' => ['pageUid' => $pageUid, 'insertPosition' => 'inside']]);
|
||||
}
|
||||
$view->assign('hasNewPagesData', $hasNewPagesData);
|
||||
}
|
||||
|
||||
return $view->renderResponse('Page/NewPages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist new pages in DB
|
||||
*
|
||||
* @param array $newPagesData Data array with title and page type
|
||||
* @param int $pageUid Uid of page new pages should be added in
|
||||
* @param bool $afterExisting True if new pages should be created after existing pages
|
||||
* @param bool $hidePages True if new pages should be set to hidden
|
||||
* @param bool $hidePagesInMenu True if new pages should be set to hidden in menu
|
||||
* @return bool TRUE if at least on pages has been added
|
||||
*/
|
||||
protected function createPages(array $newPagesData, int $pageUid, bool $afterExisting, bool $hidePages, bool $hidePagesInMenu): bool
|
||||
{
|
||||
$pagesCreated = false;
|
||||
|
||||
// Set first pid to "-1 * uid of last existing sub page" if pages should be created at end
|
||||
$firstPid = $pageUid;
|
||||
if ($afterExisting) {
|
||||
$subPages = $this->getSubPagesOfPage($pageUid);
|
||||
$lastPage = end($subPages);
|
||||
if (isset($lastPage['uid']) && MathUtility::canBeInterpretedAsInteger($lastPage['uid'])) {
|
||||
$firstPid = -(int)$lastPage['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
$dataMap = [];
|
||||
$firstRecord = true;
|
||||
$previousIdentifier = '';
|
||||
foreach ($newPagesData as $identifier => $data) {
|
||||
if (!trim($data['title'])) {
|
||||
continue;
|
||||
}
|
||||
$dataMap['pages'][$identifier]['hidden'] = (int)$hidePages;
|
||||
$dataMap['pages'][$identifier]['nav_hide'] = (int)$hidePagesInMenu;
|
||||
$dataMap['pages'][$identifier]['title'] = $data['title'];
|
||||
$dataMap['pages'][$identifier]['doktype'] = $data['doktype'];
|
||||
if ($firstRecord) {
|
||||
$firstRecord = false;
|
||||
$dataMap['pages'][$identifier]['pid'] = $firstPid;
|
||||
} else {
|
||||
$dataMap['pages'][$identifier]['pid'] = '-' . $previousIdentifier;
|
||||
}
|
||||
$previousIdentifier = $identifier;
|
||||
}
|
||||
|
||||
if (!empty($dataMap)) {
|
||||
$pagesCreated = true;
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->start($dataMap, []);
|
||||
$dataHandler->process_datamap();
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
|
||||
return $pagesCreated;
|
||||
}
|
||||
|
||||
protected function filterTypesThatOnlyRequireTitle(array $selectData, array &$filteredTypes): array
|
||||
{
|
||||
$pageSchema = $this->tcaSchemaFactory->get('pages');
|
||||
|
||||
foreach ($selectData as $group => $types) {
|
||||
foreach ($types as $index => $type) {
|
||||
$typeValue = (string)$type['value'];
|
||||
$schema = $pageSchema->getSubSchema($typeValue);
|
||||
foreach ($schema->getFields() as $field) {
|
||||
if ($field->isRequired() && $field->getName() !== 'title') {
|
||||
unset($selectData[$group][$index]);
|
||||
$filteredTypes[$typeValue] = $type['label'];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty categories
|
||||
$selectData = array_filter($selectData, static fn(array $types) => count($types) > 0);
|
||||
|
||||
return $selectData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page selector type data
|
||||
*/
|
||||
protected function getTypeSelectData(int $pageUid, ServerRequestInterface $request): array
|
||||
{
|
||||
$formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
|
||||
$formDataGroup->setProviderList([
|
||||
InitializeProcessedTca::class,
|
||||
DatabaseParentPageRow::class,
|
||||
DatabaseUserPermissionCheck::class,
|
||||
DatabaseEffectivePid::class,
|
||||
UserTsConfig::class,
|
||||
PageTsConfig::class,
|
||||
DatabaseRowInitializeNew::class,
|
||||
DatabaseUniqueUidNewRow::class,
|
||||
TcaSelectItems::class,
|
||||
]);
|
||||
$selectItems = $this->formDataCompiler->compile(
|
||||
[
|
||||
'command' => 'new',
|
||||
'request' => $request,
|
||||
'tableName' => 'pages',
|
||||
'vanillaUid' => $pageUid,
|
||||
],
|
||||
$formDataGroup
|
||||
)['processedTca']['columns']['doktype']['config']['items'] ?? [];
|
||||
|
||||
$groupedData = [];
|
||||
$groupLabel = '';
|
||||
|
||||
foreach ($selectItems as $selectItem) {
|
||||
// If it is a group, save the group label for the children underneath.
|
||||
if ($selectItem['value'] === '--div--') {
|
||||
// Dividers defined inside the items array are not translated
|
||||
// by the GroupAndSortService.
|
||||
$groupLabel = $this->getLanguageService()->sL($selectItem['label']);
|
||||
} else {
|
||||
$groupedData[$groupLabel][] = $selectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return $groupedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sub pages with some all fields from given page.
|
||||
* Fetch all data fields for full page icon display
|
||||
*
|
||||
* @param int $pageUid Get sub pages from this pages
|
||||
*/
|
||||
protected function getSubPagesOfPage(int $pageUid): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageUid, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
$this->tcaSchemaFactory->get('pages')->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName(),
|
||||
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->orderBy('sorting')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?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\Controller\Page;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
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\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* "Sort sub pages" controller - reachable from context menu "more" on page records
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class SortSubPagesController
|
||||
{
|
||||
public function __construct(
|
||||
protected IconFactory $iconFactory,
|
||||
protected ComponentFactory $componentFactory,
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Main function Handling input variables and rendering main view.
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$backendUser = $this->getBackendUser();
|
||||
$parentPageUid = (int)($request->getQueryParams()['id'] ?? 0);
|
||||
|
||||
// Show only if there is a valid page and if this page may be viewed by the user
|
||||
$pageInformation = BackendUtility::readPageAccess($parentPageUid, $backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if (!is_array($pageInformation)) {
|
||||
// User has no permission on parent page, should not happen, just render an empty page
|
||||
return $view->renderResponse('Dummy/Index');
|
||||
}
|
||||
|
||||
// Doc header handling
|
||||
$view->getDocHeaderComponent()->setPageBreadcrumb($pageInformation);
|
||||
$view->addButtonToButtonBar(
|
||||
$this->componentFactory->createViewButton(
|
||||
PreviewUriBuilder::create($pageInformation)
|
||||
->withRootLine(BackendUtility::BEgetRootLine($parentPageUid))
|
||||
->buildDispatcherDataAttributes() ?? []
|
||||
)
|
||||
);
|
||||
|
||||
$isInWorkspace = $backendUser->workspace !== 0;
|
||||
$view->assignMultiple([
|
||||
'isInWorkspace' => $isInWorkspace,
|
||||
'maxTitleLength' => $backendUser->uc['titleLen'] ?? 20,
|
||||
'parentPageUid' => $parentPageUid,
|
||||
'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'],
|
||||
'timeFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
|
||||
]);
|
||||
|
||||
if (!$isInWorkspace) {
|
||||
// Apply new sorting if given
|
||||
$newSortBy = $request->getQueryParams()['newSortBy'] ?? null;
|
||||
if ($newSortBy && in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) {
|
||||
$this->sortSubPagesByField($parentPageUid, (string)$newSortBy);
|
||||
} elseif ($newSortBy && $newSortBy === 'reverseCurrentSorting') {
|
||||
$this->reverseSortingOfPages($parentPageUid);
|
||||
}
|
||||
|
||||
// Get sub pages, loop through them and add page/user specific permission details
|
||||
$pageRecords = $this->getSubPagesOfPage($parentPageUid);
|
||||
$hasInvisiblePage = false;
|
||||
$subPages = [];
|
||||
foreach ($pageRecords as $page) {
|
||||
$pageWithPermissions = [];
|
||||
$pageWithPermissions['record'] = $page;
|
||||
$calculatedPermissions = new Permission($backendUser->calcPerms($page));
|
||||
$pageWithPermissions['canEdit'] = $backendUser->isAdmin() || $calculatedPermissions->editPagePermissionIsGranted();
|
||||
$canSeePage = $backendUser->isAdmin() || $calculatedPermissions->showPagePermissionIsGranted();
|
||||
if ($canSeePage) {
|
||||
$subPages[] = $pageWithPermissions;
|
||||
} else {
|
||||
$hasInvisiblePage = true;
|
||||
}
|
||||
}
|
||||
$view->assign('subPages', $subPages);
|
||||
$view->assign('hasInvisiblePage', $hasInvisiblePage);
|
||||
}
|
||||
|
||||
return $view->renderResponse('Page/SortSubPages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort sub pages of given uid by field name alphabetically
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
* @param string $newSortBy Field name to sort by
|
||||
* @throws \RuntimeException If $newSortBy does not validate
|
||||
*/
|
||||
protected function sortSubPagesByField(int $parentPageUid, string $newSortBy)
|
||||
{
|
||||
if (!in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) {
|
||||
throw new \RuntimeException(
|
||||
'New sort by must be one of "title", "subtitle", "nav_title", "crdate" or tstamp',
|
||||
1498924810
|
||||
);
|
||||
}
|
||||
$subPages = $this->getSubPagesOfPage($parentPageUid, $newSortBy);
|
||||
if (!empty($subPages)) {
|
||||
$subPages = array_reverse($subPages);
|
||||
$this->persistNewSubPageOrder($parentPageUid, $subPages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse current sorting of sub pages
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
*/
|
||||
protected function reverseSortingOfPages(int $parentPageUid)
|
||||
{
|
||||
$subPages = $this->getSubPagesOfPage($parentPageUid);
|
||||
if (!empty($subPages)) {
|
||||
$this->persistNewSubPageOrder($parentPageUid, $subPages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store new sub page order
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
* @param array $subPages List of sub pages in new order
|
||||
*/
|
||||
protected function persistNewSubPageOrder(int $parentPageUid, array $subPages)
|
||||
{
|
||||
$commandArray = [];
|
||||
foreach ($subPages as $subPage) {
|
||||
$commandArray['pages'][$subPage['uid']]['move'] = $parentPageUid;
|
||||
}
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->start([], $commandArray);
|
||||
$dataHandler->process_cmdmap();
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sub pages with some all fields from given page.
|
||||
* Fetch all data fields for full page icon display
|
||||
*
|
||||
* @param int $parentPageUid Get sub pages from this pages
|
||||
* @param string $orderBy Order pages by this field
|
||||
*/
|
||||
protected function getSubPagesOfPage(int $parentPageUid, string $orderBy = 'sorting'): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->or(
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter(\Local\Multilanguage\Service\DefaultLanguageTagService::getTag())),
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter('')),
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($parentPageUid, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->orderBy($orderBy)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
<?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\Controller\Page;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent;
|
||||
use TYPO3\CMS\Backend\Dto\Tree\Label\Label;
|
||||
use TYPO3\CMS\Backend\Dto\Tree\PageTreeItem;
|
||||
use TYPO3\CMS\Backend\Dto\Tree\TreeItem;
|
||||
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEffectivePid;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowInitializeNew;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUniqueUidNewRow;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProvider\UserTsConfig;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Authentication\JsConfirmation;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DocumentTypeExclusionRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Controller providing data to the page tree
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
class TreeController
|
||||
{
|
||||
/**
|
||||
* Option to use the nav_title field for outputting in the tree items, set via userTS.
|
||||
*/
|
||||
protected bool $useNavTitle = false;
|
||||
|
||||
/**
|
||||
* Option to prefix the page ID when outputting the tree items, set via userTS.
|
||||
*/
|
||||
protected bool $addIdAsPrefix = false;
|
||||
|
||||
/**
|
||||
* Option to prefix the domain name of sys_domains when outputting the tree items, set via userTS.
|
||||
*/
|
||||
protected bool $addDomainName = false;
|
||||
|
||||
/**
|
||||
* Option to add the rootline path above each mount point, set via userTS.
|
||||
*/
|
||||
protected bool $showMountPathAboveMounts = false;
|
||||
|
||||
/**
|
||||
* A list of pages not to be shown.
|
||||
*/
|
||||
protected array $hiddenRecords = [];
|
||||
|
||||
/**
|
||||
* An array of labels for a branch in the tree, set via userTS.
|
||||
*/
|
||||
protected array $labels = [];
|
||||
|
||||
/**
|
||||
* Number of tree levels which should be returned on the first page tree load
|
||||
*/
|
||||
protected int $levelsToFetch = 2;
|
||||
|
||||
/**
|
||||
* When set to true all nodes returend by API will be expanded
|
||||
*/
|
||||
protected bool $expandAllNodes = false;
|
||||
|
||||
/**
|
||||
* Used in the record link picker to limit the page tree only to a specific list
|
||||
* of alternative entry points for selecting only from a list of pages
|
||||
*/
|
||||
protected array $alternativeEntryPoints = [];
|
||||
|
||||
protected PageTreeRepository $pageTreeRepository;
|
||||
|
||||
protected bool $userHasAccessToModifyPagesAndToDefaultLanguage = false;
|
||||
|
||||
public function __construct(
|
||||
protected readonly IconFactory $iconFactory,
|
||||
protected readonly UriBuilder $uriBuilder,
|
||||
protected readonly EventDispatcherInterface $eventDispatcher,
|
||||
protected readonly SiteFinder $siteFinder,
|
||||
protected readonly PageDoktypeRegistry $pageDoktypeRegistry,
|
||||
protected readonly FormDataCompiler $formDataCompiler,
|
||||
) {}
|
||||
|
||||
protected function initializeConfiguration(ServerRequestInterface $request)
|
||||
{
|
||||
if ($request->getQueryParams()['readOnly'] ?? false) {
|
||||
$this->getBackendUser()->initializeWebmountsForElementBrowser();
|
||||
}
|
||||
if ($request->getQueryParams()['alternativeEntryPoints'] ?? false) {
|
||||
$this->alternativeEntryPoints = $request->getQueryParams()['alternativeEntryPoints'];
|
||||
$this->alternativeEntryPoints = array_filter($this->alternativeEntryPoints, function (int $pageId): bool {
|
||||
return $this->getBackendUser()->isInWebMount($pageId) !== null;
|
||||
});
|
||||
$this->alternativeEntryPoints = array_map(intval(...), $this->alternativeEntryPoints);
|
||||
$this->alternativeEntryPoints = array_unique($this->alternativeEntryPoints);
|
||||
}
|
||||
$userTsConfig = $this->getBackendUser()->getTSConfig();
|
||||
$this->hiddenRecords = GeneralUtility::intExplode(
|
||||
',',
|
||||
(string)($userTsConfig['options.']['hideRecords.']['pages'] ?? ''),
|
||||
true
|
||||
);
|
||||
$this->labels = $userTsConfig['options.']['pageTree.']['label.'] ?? [];
|
||||
$this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
|
||||
$this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
|
||||
$this->useNavTitle = (bool)($userTsConfig['options.']['pageTree.']['showNavTitle'] ?? false);
|
||||
$this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
|
||||
$this->userHasAccessToModifyPagesAndToDefaultLanguage = $this->getBackendUser()->check('tables_modify', 'pages') && $this->getBackendUser()->checkLanguageAccess(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page tree configuration in JSON
|
||||
*/
|
||||
public function fetchConfigurationAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$userTsConfig = $backendUser->getTSConfig();
|
||||
|
||||
// Check if translation search feature is generally available (TSconfig setting)
|
||||
$translationSearchAvailable = (bool)($userTsConfig['options.']['pageTree.']['searchInTranslatedPages'] ?? true);
|
||||
|
||||
// Determine if translation search is enabled by the user preference - otherwise TSconfig setting applies
|
||||
$translationSearchEnabled = $translationSearchAvailable
|
||||
&& (
|
||||
!isset($backendUser->uc['pageTree_searchInTranslatedPages'])
|
||||
|| $backendUser->uc['pageTree_searchInTranslatedPages']
|
||||
);
|
||||
|
||||
// Check if frontend URI search feature is generally available (TSconfig setting)
|
||||
$frontendUriSearchAvailable = (bool)($userTsConfig['options.']['pageTree.']['searchByFrontendUri'] ?? true);
|
||||
|
||||
// Determine if frontend URI search is enabled by the user preference - otherwise TSconfig setting applies
|
||||
$frontendUriSearchEnabled = $frontendUriSearchAvailable
|
||||
&& (
|
||||
!isset($backendUser->uc['pageTree_searchByFrontendUri'])
|
||||
|| $backendUser->uc['pageTree_searchByFrontendUri']
|
||||
);
|
||||
|
||||
// Build language list from site configuration
|
||||
$languages = [];
|
||||
$currentLanguageTag = $_COOKIE['pageTreeLang'] ?? '';
|
||||
try {
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
$sites = $siteFinder->getAllSites();
|
||||
foreach ($sites as $site) {
|
||||
foreach ($site->getAllLanguages() as $lang) {
|
||||
$tag = $lang->getLanguageTag();
|
||||
$languages[] = [
|
||||
'languageTag' => $tag,
|
||||
'title' => $lang->getTitle(),
|
||||
'flag' => $lang->getFlagIdentifier(),
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable) {}
|
||||
|
||||
$dataUrlParams = [];
|
||||
if ($currentLanguageTag !== '') {
|
||||
$dataUrlParams['language'] = $currentLanguageTag;
|
||||
}
|
||||
|
||||
$configuration = [
|
||||
'allowDragMove' => $this->isDragMoveAllowed(),
|
||||
'doktypes' => $this->getDokTypes($request),
|
||||
'displayDeleteConfirmation' => $backendUser->jsConfirmation(JsConfirmation::DELETE),
|
||||
'temporaryMountPoint' => $this->getMountPointPath((int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0)),
|
||||
'showIcons' => true,
|
||||
'dataUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_data', $dataUrlParams),
|
||||
'rootlineUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_rootline'),
|
||||
'filterUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_filter'),
|
||||
'setTemporaryMountPointUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_set_temporary_mount_point'),
|
||||
'searchInTranslatedPagesEnabled' => $translationSearchEnabled,
|
||||
'searchInTranslatedPagesAvailable' => $translationSearchAvailable,
|
||||
'searchByFrontendUriEnabled' => $frontendUriSearchEnabled,
|
||||
'searchByFrontendUriAvailable' => $frontendUriSearchAvailable,
|
||||
'languages' => $languages,
|
||||
'currentLanguage' => $currentLanguageTag,
|
||||
];
|
||||
|
||||
return new JsonResponse($configuration);
|
||||
}
|
||||
|
||||
public function fetchReadOnlyConfigurationAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$entryPoints = (string)($request->getQueryParams()['alternativeEntryPoints'] ?? '');
|
||||
$entryPoints = GeneralUtility::intExplode(',', $entryPoints, true);
|
||||
$additionalArguments = [
|
||||
'readOnly' => 1,
|
||||
];
|
||||
if (!empty($entryPoints)) {
|
||||
$additionalArguments['alternativeEntryPoints'] = $entryPoints;
|
||||
}
|
||||
$configuration = [
|
||||
'displayDeleteConfirmation' => $this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE),
|
||||
'temporaryMountPoint' => $this->getMountPointPath((int)($this->getBackendUser()->uc['pageTree_temporaryMountPoint'] ?? 0)),
|
||||
'showIcons' => true,
|
||||
'dataUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_data', $additionalArguments),
|
||||
'filterUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_filter', $additionalArguments),
|
||||
'setTemporaryMountPointUrl' => (string)$this->uriBuilder->buildUriFromRoute('ajax_page_tree_set_temporary_mount_point'),
|
||||
'nonViewableDoktypes' => $this->pageDoktypeRegistry->getNonViewableDoktypes(),
|
||||
];
|
||||
return new JsonResponse($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of doktypes to display in page tree toolbar drag area,
|
||||
* automatically determined based on the user's group permissions.
|
||||
*/
|
||||
protected function getDokTypes(ServerRequestInterface $request): array
|
||||
{
|
||||
$formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
|
||||
// Skip DatabaseUserPermissionCheck::class to return doktypes even if the user cannot create pages at root level
|
||||
$formDataGroup->setProviderList([
|
||||
InitializeProcessedTca::class,
|
||||
DatabaseParentPageRow::class,
|
||||
DatabaseEffectivePid::class,
|
||||
UserTsConfig::class,
|
||||
PageTsConfig::class,
|
||||
DatabaseRowInitializeNew::class,
|
||||
DatabaseUniqueUidNewRow::class,
|
||||
TcaSelectItems::class,
|
||||
]);
|
||||
|
||||
try {
|
||||
$doktypes = $this->formDataCompiler
|
||||
->compile(
|
||||
[
|
||||
'command' => 'new',
|
||||
'request' => $request,
|
||||
'tableName' => 'pages',
|
||||
'vanillaUid' => 0,
|
||||
],
|
||||
$formDataGroup
|
||||
)['processedTca']['columns']['doktype']['config']['items'] ?? [];
|
||||
} catch (\Exception) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_map(
|
||||
static fn(array $doktype) => [
|
||||
'nodeType' => $doktype['value'],
|
||||
'icon' => $doktype['icon'] ?? '',
|
||||
'title' => $doktype['label'] ?? '',
|
||||
],
|
||||
array_filter(
|
||||
$doktypes,
|
||||
static fn(array $doktype) => ($doktype['value'] ?? '') !== '--div--' && ($doktype['value'] ?? '') !== ''
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representing page tree
|
||||
*/
|
||||
public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->initializeConfiguration($request);
|
||||
|
||||
$languageParam = $request->getQueryParams()['language'] ?? '';
|
||||
$languageTag = ($languageParam !== '' && $languageParam !== '0') ? $languageParam : null;
|
||||
|
||||
$items = [];
|
||||
$parentIdentifier = $request->getQueryParams()['parent'] ?? null;
|
||||
if ($parentIdentifier) {
|
||||
$parentDepth = (int)($request->getQueryParams()['depth'] ?? 0);
|
||||
// Fetching a part of a page tree
|
||||
$entryPoints = $this->getAllEntryPointPageTrees((int)$parentIdentifier);
|
||||
$mountPid = (int)($request->getQueryParams()['mount'] ?? 0);
|
||||
$this->levelsToFetch = $parentDepth + $this->levelsToFetch;
|
||||
foreach ($entryPoints as $page) {
|
||||
$items[] = $this->pagesToFlatArray($page, $mountPid, $parentDepth);
|
||||
}
|
||||
} else {
|
||||
$entryPoints = $this->getAllEntryPointPageTrees();
|
||||
foreach ($entryPoints as $page) {
|
||||
$items[] = $this->pagesToFlatArray($page, (int)$page['uid']);
|
||||
}
|
||||
}
|
||||
$items = array_merge(...$items);
|
||||
|
||||
if ($languageTag !== null) {
|
||||
$this->applyTranslationOverlay($items, $languageTag);
|
||||
}
|
||||
|
||||
return new JsonResponse($this->getPostProcessedPageItems($request, $items));
|
||||
}
|
||||
|
||||
private function applyTranslationOverlay(array &$items, string $languageTag): void
|
||||
{
|
||||
$pageIds = [];
|
||||
foreach ($items as $item) {
|
||||
$uid = (int)($item['identifier'] ?? 0);
|
||||
if ($uid > 0) {
|
||||
$pageIds[] = $uid;
|
||||
}
|
||||
}
|
||||
if (empty($pageIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$translations = $queryBuilder
|
||||
->select('l10n_parent', 'title', 'nav_title', 'language_tag')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->in('l10n_parent', $pageIds),
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter($languageTag))
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$translationMap = [];
|
||||
foreach ($translations as $trans) {
|
||||
$translationMap[(int)$trans['l10n_parent']] = $trans;
|
||||
}
|
||||
|
||||
foreach ($items as &$item) {
|
||||
$page = $item['_page'] ?? [];
|
||||
$uid = (int)($page['uid'] ?? (int)($item['identifier'] ?? 0));
|
||||
$trans = $translationMap[$uid] ?? null;
|
||||
if ($trans) {
|
||||
$item['name'] = $trans['title'];
|
||||
if (!empty($trans['nav_title'])) {
|
||||
$item['name'] = $trans['nav_title'];
|
||||
$item['nameSourceField'] = 'nav_title';
|
||||
}
|
||||
$item['_page']['title'] = $trans['title'];
|
||||
$item['_page']['nav_title'] = $trans['nav_title'] ?? '';
|
||||
$item['_page']['_translatedTitle'] = $trans['title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representing page rootline
|
||||
*/
|
||||
public function fetchRootlineAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$identifier = (string)($request->getQueryParams()['identifier'] ?? '');
|
||||
if (!MathUtility::canBeInterpretedAsInteger($identifier)) {
|
||||
return new JsonResponse(null, 400);
|
||||
}
|
||||
$pageId = (int)$identifier;
|
||||
|
||||
if ($pageId === 0) {
|
||||
return new JsonResponse(['rootline' => ['0']]);
|
||||
}
|
||||
|
||||
$rootline = BackendUtility::BEgetRootLine((int)$identifier);
|
||||
if ($rootline === []) {
|
||||
return new JsonResponse(null, 404);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'rootline' => array_map(strval(...), array_column(array_reverse($rootline), 'uid')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representing page tree filtered by keyword
|
||||
*/
|
||||
public function filterDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$searchQuery = $request->getQueryParams()['q'] ?? '';
|
||||
if (trim($searchQuery) === '') {
|
||||
return new JsonResponse([]);
|
||||
}
|
||||
|
||||
$this->initializeConfiguration($request);
|
||||
$this->expandAllNodes = true;
|
||||
|
||||
$items = [];
|
||||
$entryPoints = $this->getAllEntryPointPageTrees(0, $searchQuery);
|
||||
|
||||
foreach ($entryPoints as $page) {
|
||||
if (!empty($page)) {
|
||||
$items[] = $this->pagesToFlatArray($page, (int)$page['uid']);
|
||||
}
|
||||
}
|
||||
$items = array_merge(...$items);
|
||||
|
||||
return new JsonResponse($this->getPostProcessedPageItems($request, $items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a temporary mount point
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setTemporaryMountPointAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (empty($request->getParsedBody()['pid'])) {
|
||||
throw new \RuntimeException(
|
||||
'Required "pid" parameter is missing.',
|
||||
1511792197
|
||||
);
|
||||
}
|
||||
$pid = (int)$request->getParsedBody()['pid'];
|
||||
|
||||
$this->getBackendUser()->uc['pageTree_temporaryMountPoint'] = $pid;
|
||||
$this->getBackendUser()->writeUC();
|
||||
$response = [
|
||||
'mountPointPath' => $this->getMountPointPath($pid),
|
||||
];
|
||||
return new JsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts nested tree structure produced by PageTreeRepository to a flat, one level array
|
||||
* and also adds visual representation information to the data.
|
||||
*
|
||||
* The result is intended to be used as JSON result - dumping data directly to HTML might lead to XSS!
|
||||
*
|
||||
* @param array $page
|
||||
* @param int $entryPoint
|
||||
* @param int $depth
|
||||
*/
|
||||
protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pageId = (int)$page['uid'];
|
||||
if (in_array($pageId, $this->hiddenRecords, true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stopPageTree = !empty($page['php_tree_stop']) && $depth > 0;
|
||||
$identifier = $entryPoint . '_' . $pageId;
|
||||
|
||||
$suffix = '';
|
||||
$prefix = '';
|
||||
$nameSourceField = 'title';
|
||||
$visibleText = $page['title'];
|
||||
$tooltip = BackendUtility::titleAttribForPages($page, '', false, $this->useNavTitle);
|
||||
if ($pageId !== 0) {
|
||||
$icon = $this->iconFactory->getIconForRecord('pages', $page, IconSize::SMALL);
|
||||
} else {
|
||||
$icon = $this->iconFactory->getIcon('apps-pagetree-root', IconSize::SMALL);
|
||||
}
|
||||
|
||||
if ($this->useNavTitle && trim($page['nav_title'] ?? '') !== '') {
|
||||
$nameSourceField = 'nav_title';
|
||||
$visibleText = $page['nav_title'];
|
||||
}
|
||||
if (trim($visibleText) === '') {
|
||||
$visibleText = htmlspecialchars('[' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title') . ']');
|
||||
}
|
||||
|
||||
if ($this->addDomainName && ($page['is_siteroot'] ?? false)) {
|
||||
$domain = $this->getDomainNameForPage($pageId);
|
||||
$suffix = $domain !== '' ? ' [' . $domain . ']' : '';
|
||||
}
|
||||
|
||||
$lockInfo = BackendUtility::isRecordLocked('pages', $pageId);
|
||||
if (is_array($lockInfo)) {
|
||||
$tooltip .= ' - ' . $lockInfo['msg'];
|
||||
}
|
||||
if ($this->addIdAsPrefix) {
|
||||
$prefix = '[' . $pageId . '] ';
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
if (!empty($this->labels[$pageId . '.']) && isset($this->labels[$pageId . '.']['label']) && trim($this->labels[$pageId . '.']['label']) !== '') {
|
||||
$labels[] = new Label(
|
||||
label: $this->getLanguageService()->sL($this->labels[$pageId . '.']['label']),
|
||||
color: (string)($this->labels[$pageId . '.']['color'] ?? '#ff8700'),
|
||||
);
|
||||
}
|
||||
|
||||
$editable = false;
|
||||
if ($pageId !== 0) {
|
||||
$editable = $this->userHasAccessToModifyPagesAndToDefaultLanguage && $backendUser->doesUserHaveAccess($page, Permission::PAGE_EDIT);
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$item = [
|
||||
// identifier is not only used for pages, therefore it's a string
|
||||
'identifier' => (string)$pageId,
|
||||
'parentIdentifier' => (string)($page['pid'] ?? ''),
|
||||
'recordType' => 'pages',
|
||||
'name' => $visibleText,
|
||||
'prefix' => !empty($prefix) ? htmlspecialchars($prefix) : '',
|
||||
'suffix' => !empty($suffix) ? htmlspecialchars($suffix) : '',
|
||||
'tooltip' => $tooltip,
|
||||
'depth' => $depth,
|
||||
'icon' => $icon->getIdentifier(),
|
||||
'overlayIcon' => $icon->getOverlayIcon() ? $icon->getOverlayIcon()->getIdentifier() : '',
|
||||
'editable' => $editable,
|
||||
'deletable' => $backendUser->doesUserHaveAccess($page, Permission::PAGE_DELETE),
|
||||
'labels' => $labels,
|
||||
|
||||
// _page is only for use in events so they do not need to fetch those
|
||||
// records again. The property will be removed from the final payload.
|
||||
'_page' => $page,
|
||||
// _translationLanguageUids contains the language UIDs for translations that matched (only populated during search)
|
||||
'_translationLanguageUids' => $this->pageTreeRepository->getTranslationMatches($pageId),
|
||||
'doktype' => (int)($page['doktype'] ?? 0),
|
||||
'nameSourceField' => $nameSourceField,
|
||||
'mountPoint' => $entryPoint,
|
||||
'workspaceId' => !empty($page['t3ver_oid']) ? $page['t3ver_oid'] : $pageId,
|
||||
];
|
||||
|
||||
if (!empty($page['_children']) || $this->pageTreeRepository->hasChildren($pageId)) {
|
||||
$item['hasChildren'] = true;
|
||||
if ($depth >= $this->levelsToFetch) {
|
||||
$page = $this->pageTreeRepository->getTreeLevels($page, 1);
|
||||
}
|
||||
}
|
||||
if (is_array($lockInfo)) {
|
||||
$item['locked'] = true;
|
||||
}
|
||||
if ($stopPageTree) {
|
||||
$item['stopPageTree'] = true;
|
||||
}
|
||||
if ($depth === 0) {
|
||||
if ($this->showMountPathAboveMounts) {
|
||||
$item['note'] = $this->getMountPointPath($pageId);
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
if (!$stopPageTree && is_array($page['_children']) && !empty($page['_children']) && ($depth < $this->levelsToFetch || $this->expandAllNodes)) {
|
||||
$items[key($items)]['loaded'] = true;
|
||||
foreach ($page['_children'] as $child) {
|
||||
$items = array_merge($items, $this->pagesToFlatArray($child, $entryPoint, $depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function initializePageTreeRepository(): PageTreeRepository
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$userTsConfig = $backendUser->getTSConfig();
|
||||
$excludedDocumentTypes = GeneralUtility::intExplode(',', (string)($userTsConfig['options.']['pageTree.']['excludeDoktypes'] ?? ''), true);
|
||||
|
||||
$additionalQueryRestrictions = [];
|
||||
if ($excludedDocumentTypes !== []) {
|
||||
$additionalQueryRestrictions[] = GeneralUtility::makeInstance(DocumentTypeExclusionRestriction::class, $excludedDocumentTypes);
|
||||
}
|
||||
|
||||
$pageTreeRepository = GeneralUtility::makeInstance(
|
||||
PageTreeRepository::class,
|
||||
$backendUser->workspace,
|
||||
[],
|
||||
$additionalQueryRestrictions
|
||||
);
|
||||
$pageTreeRepository->setAdditionalWhereClause($backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
return $pageTreeRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all pages for all tree entry points the user is allowed to see
|
||||
*
|
||||
* @param string $query The search query can either be a string to be found in the title or the nav_title of a page or the uid of a page.
|
||||
*/
|
||||
protected function getAllEntryPointPageTrees(int $startPid = 0, string $query = ''): array
|
||||
{
|
||||
$this->pageTreeRepository ??= $this->initializePageTreeRepository();
|
||||
$backendUser = $this->getBackendUser();
|
||||
if ($startPid === 0) {
|
||||
$startPid = (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0);
|
||||
}
|
||||
|
||||
$entryPointIds = null;
|
||||
if ($startPid > 0) {
|
||||
$entryPointIds = [$startPid];
|
||||
} elseif (!empty($this->alternativeEntryPoints)) {
|
||||
$entryPointIds = $this->alternativeEntryPoints;
|
||||
}
|
||||
|
||||
$permClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
if ($query !== '') {
|
||||
$this->levelsToFetch = 999;
|
||||
$this->pageTreeRepository->fetchFilteredTree(
|
||||
$query,
|
||||
$this->getAllowedMountPoints(),
|
||||
$permClause
|
||||
);
|
||||
}
|
||||
$rootRecord = [
|
||||
'uid' => 0,
|
||||
'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3',
|
||||
];
|
||||
$entryPointRecords = [];
|
||||
$mountPoints = [];
|
||||
if ($entryPointIds === null) {
|
||||
//watch out for deleted pages returned as webmount
|
||||
$mountPoints = $backendUser->getWebmounts();
|
||||
$mountPoints = array_filter($mountPoints, fn(int $id): bool => !in_array($id, $this->hiddenRecords, true));
|
||||
|
||||
// Switch to multiple-entryPoint-mode if the rootPage is to be mounted.
|
||||
// (other mounts would appear duplicated in the pid = 0 tree otherwise)
|
||||
if (in_array(0, $mountPoints, true)) {
|
||||
$entryPointIds = $mountPoints;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entryPointIds === null) {
|
||||
if ($query !== '') {
|
||||
$rootRecord = $this->pageTreeRepository->getTree(0, null, $mountPoints);
|
||||
} else {
|
||||
$rootRecord = $this->pageTreeRepository->getTreeLevels($rootRecord, $this->levelsToFetch, $mountPoints);
|
||||
}
|
||||
|
||||
$mountPointOrdering = array_flip($mountPoints);
|
||||
if (isset($rootRecord['_children'])) {
|
||||
usort($rootRecord['_children'], static function ($a, $b) use ($mountPointOrdering) {
|
||||
return ($mountPointOrdering[$a['uid']] ?? 0) <=> ($mountPointOrdering[$b['uid']] ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
$entryPointRecords[] = $rootRecord;
|
||||
} else {
|
||||
$entryPointIds = array_filter($entryPointIds, fn(int $id): bool => !in_array($id, $this->hiddenRecords, true));
|
||||
foreach ($entryPointIds as $k => $entryPointId) {
|
||||
if ($entryPointId === 0) {
|
||||
$entryPointRecord = $rootRecord;
|
||||
} else {
|
||||
$entryPointRecord = BackendUtility::getRecordWSOL('pages', $entryPointId, '*', $permClause);
|
||||
|
||||
if ($entryPointRecord !== null && !$backendUser->isInWebMount($entryPointId)) {
|
||||
$entryPointRecord = null;
|
||||
}
|
||||
if ($entryPointRecord === null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$entryPointRecord['uid'] = (int)$entryPointRecord['uid'];
|
||||
if ($query === '') {
|
||||
$entryPointRecord = $this->pageTreeRepository->getTreeLevels($entryPointRecord, $this->levelsToFetch);
|
||||
} else {
|
||||
$entryPointRecord = $this->pageTreeRepository->getTree($entryPointRecord['uid'], null, $entryPointIds);
|
||||
}
|
||||
|
||||
if ($entryPointRecord !== []) {
|
||||
$entryPointRecords[$k] = $entryPointRecord;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $entryPointRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first configured domain name for a page
|
||||
*/
|
||||
protected function getDomainNameForPage(int $pageId): string
|
||||
{
|
||||
try {
|
||||
$site = $this->siteFinder->getSiteByRootPageId($pageId);
|
||||
return (string)$site->getBase();
|
||||
} catch (SiteNotFoundException) {
|
||||
// No site found
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mount point path for a temporary mount or the given id
|
||||
*/
|
||||
protected function getMountPointPath(int $uid): string
|
||||
{
|
||||
if ($uid <= 0) {
|
||||
return '';
|
||||
}
|
||||
$rootline = array_reverse(BackendUtility::BEgetRootLine($uid));
|
||||
array_shift($rootline);
|
||||
$path = [];
|
||||
foreach ($rootline as $rootlineElement) {
|
||||
$record = BackendUtility::getRecordWSOL('pages', $rootlineElement['uid'], 'title, nav_title', '', true, true);
|
||||
$text = $record['title'];
|
||||
if ($this->useNavTitle && trim($record['nav_title'] ?? '') !== '') {
|
||||
$text = $record['nav_title'];
|
||||
}
|
||||
$path[] = htmlspecialchars($text);
|
||||
}
|
||||
return '/' . implode('/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if drag-move in the svg tree is allowed for the user
|
||||
*/
|
||||
protected function isDragMoveAllowed(): bool
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
return $backendUser->isAdmin()
|
||||
|| ($backendUser->check('tables_modify', 'pages') && $backendUser->checkLanguageAccess(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed mountpoints. Returns temporary mountpoint when temporary mountpoint is used.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getAllowedMountPoints(): array
|
||||
{
|
||||
$mountPoints = (int)($this->getBackendUser()->uc['pageTree_temporaryMountPoint'] ?? 0);
|
||||
if (!$mountPoints) {
|
||||
if (!empty($this->alternativeEntryPoints)) {
|
||||
return $this->alternativeEntryPoints;
|
||||
}
|
||||
return $this->getBackendUser()->getWebmounts();
|
||||
}
|
||||
return [$mountPoints];
|
||||
}
|
||||
|
||||
protected function getPostProcessedPageItems(ServerRequestInterface $request, array $items): array
|
||||
{
|
||||
return array_map(
|
||||
static function (array $item): PageTreeItem {
|
||||
return new PageTreeItem(
|
||||
// TreeItem
|
||||
new TreeItem(
|
||||
identifier: $item['identifier'],
|
||||
parentIdentifier: (string)($item['parentIdentifier'] ?? ''),
|
||||
recordType: (string)($item['recordType'] ?? ''),
|
||||
name: (string)($item['name'] ?? ''),
|
||||
note: (string)($item['note'] ?? ''),
|
||||
prefix: (string)($item['prefix'] ?? ''),
|
||||
suffix: (string)($item['suffix'] ?? ''),
|
||||
tooltip: (string)($item['tooltip'] ?? ''),
|
||||
depth: (int)($item['depth'] ?? 0),
|
||||
hasChildren: (bool)($item['hasChildren'] ?? false),
|
||||
loaded: (bool)($item['loaded'] ?? false),
|
||||
editable: (bool)($item['editable'] ?? false),
|
||||
deletable: (bool)($item['deletable'] ?? false),
|
||||
icon: (string)($item['icon'] ?? ''),
|
||||
overlayIcon: (string)($item['overlayIcon'] ?? ''),
|
||||
statusInformation: (array)($item['statusInformation'] ?? []),
|
||||
labels: (array)($item['labels'] ?? []),
|
||||
),
|
||||
// PageTreeItem
|
||||
doktype: (int)($item['doktype'] ?? ''),
|
||||
nameSourceField: (string)($item['nameSourceField'] ?? ''),
|
||||
workspaceId: (int)($item['workspaceId'] ?? 0),
|
||||
locked: (bool)($item['locked'] ?? false),
|
||||
stopPageTree: (bool)($item['stopPageTree'] ?? false),
|
||||
mountPoint: (int)($item['mountPoint'] ?? 0),
|
||||
);
|
||||
},
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AfterPageTreeItemsPreparedEvent($request, $items)
|
||||
)->getItems()
|
||||
);
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
protected function getLanguageService(): ?LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'] ?? null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user