[ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.view', 'iconIdentifier' => 'actions-view-page', 'callbackAction' => 'viewRecord', ], 'edit' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.edit', 'iconIdentifier' => 'actions-page-open', 'callbackAction' => 'editRecord', ], 'new' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.newSubpage', 'iconIdentifier' => 'actions-page-new', 'callbackAction' => 'newRecord', ], 'info' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info', 'iconIdentifier' => 'actions-document-info', 'callbackAction' => 'openInfoPopUp', ], 'qrcode' => [ 'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:showPageQrCode', 'iconIdentifier' => 'actions-qrcode', 'callbackAction' => 'showQrCode', ], 'divider1' => [ 'type' => 'divider', ], 'copy' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy', 'iconIdentifier' => 'actions-edit-copy', 'callbackAction' => 'copy', ], 'copyRelease' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy', 'iconIdentifier' => 'actions-edit-copy-release', 'callbackAction' => 'clipboardRelease', ], 'cut' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut', 'iconIdentifier' => 'actions-edit-cut', 'callbackAction' => 'cut', ], 'cutRelease' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease', 'iconIdentifier' => 'actions-edit-cut-release', 'callbackAction' => 'clipboardRelease', ], 'pasteAfter' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteafter', 'iconIdentifier' => 'actions-document-paste-after', 'callbackAction' => 'pasteAfter', ], 'pasteInto' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteinto', 'iconIdentifier' => 'actions-document-paste-into', 'callbackAction' => 'pasteInto', ], 'divider2' => [ 'type' => 'divider', ], 'more' => [ 'type' => 'submenu', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.more', 'iconIdentifier' => '', 'callbackAction' => 'openSubmenu', 'childItems' => [ 'pagesSort' => [ 'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_sort.xlf:title', 'iconIdentifier' => 'actions-page-move', 'callbackAction' => 'pagesSort', ], 'pagesNewMultiple' => [ 'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_new.xlf:title', 'iconIdentifier' => 'apps-pagetree-drag-move-between', 'callbackAction' => 'pagesNewMultiple', ], 'mountAsTreeRoot' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.tempMountPoint', 'iconIdentifier' => 'actions-pagetree-mountroot', 'callbackAction' => 'mountAsTreeRoot', ], 'showInMenus' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_showInMenus', 'iconIdentifier' => 'actions-view', 'callbackAction' => 'showInMenus', ], 'hideInMenus' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_hideInMenus', 'iconIdentifier' => 'actions-ban', 'callbackAction' => 'hideInMenus', ], ], ], 'divider3' => [ 'type' => 'divider', ], 'enable' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:enable', 'iconIdentifier' => 'actions-edit-unhide', 'callbackAction' => 'enableRecord', ], 'disable' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:disable', 'iconIdentifier' => 'actions-edit-hide', 'callbackAction' => 'disableRecord', ], 'delete' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete', 'iconIdentifier' => 'actions-edit-delete', 'callbackAction' => 'deleteRecord', ], 'history' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_history', 'iconIdentifier' => 'actions-document-history-open', 'callbackAction' => 'openHistoryPopUp', ], 'clearCache' => [ 'label' => 'core.cache:page.label', 'iconIdentifier' => 'actions-system-cache-clear', 'callbackAction' => 'clearCache', ], ]; protected bool $languageAccess = false; /** * Checks if the provider can add items to the menu */ public function canHandle(): bool { return $this->table === 'pages'; } public function getPriority(): int { return 100; } protected function canRender(string $itemName, string $type): bool { if (in_array($type, ['divider', 'submenu'], true)) { return true; } if (in_array($itemName, $this->disabledItems, true)) { return false; } $canRender = false; switch ($itemName) { case 'view': case 'qrcode': $canRender = $this->canBeViewed(); break; case 'edit': $canRender = $this->canBeEdited(); break; case 'new': case 'pagesNewMultiple': $canRender = $this->canBeCreated(); break; case 'info': $canRender = $this->canShowInfo(); break; case 'enable': $canRender = $this->canBeEnabled(); break; case 'disable': $canRender = $this->canBeDisabled(); break; case 'showInMenus': $canRender = $this->canBeToggled('nav_hide', 1); break; case 'hideInMenus': $canRender = $this->canBeToggled('nav_hide', 0); break; case 'delete': $canRender = $this->canBeDeleted(); break; case 'history': $canRender = $this->canShowHistory(); break; case 'pagesSort': $canRender = $this->canBeSorted(); break; case 'mountAsTreeRoot': $canRender = !$this->isRoot(); break; case 'copy': $canRender = $this->canBeCopied(); break; case 'copyRelease': $canRender = $this->isRecordInClipboard('copy'); break; case 'cut': $canRender = $this->canBeCut() && !$this->isRecordInClipboard('cut'); break; case 'cutRelease': $canRender = $this->isRecordInClipboard('cut'); break; case 'pasteAfter': $canRender = $this->canBePastedAfter(); break; case 'pasteInto': $canRender = $this->canBePastedInto(); break; case 'clearCache': $canRender = $this->canClearCache(); break; } return $canRender; } /** * Saves calculated permissions for a page to speed things up */ protected function initPermissions(): void { $this->pagePermissions = new Permission($this->backendUser->calcPerms($this->record)); $this->languageAccess = $this->hasLanguageAccess(); } /** * Checks if the user may create pages below the given page */ protected function canBeCreated(): bool { if (!$this->backendUser->checkLanguageAccess(0)) { return false; } if ($this->getLanguageField() !== '' && !in_array($this->record[$this->getLanguageField()] ?? false, [0, -1]) ) { return false; } if (!$this->backendUser->check('tables_modify', $this->table)) { return false; } return $this->hasPagePermission(Permission::PAGE_NEW); } /** * Checks if the user has editing rights */ protected function canBeEdited(): bool { if (!$this->languageAccess) { return false; } if ($this->isRoot()) { return false; } if ($this->getSchema()?->hasCapability(TcaSchemaCapability::AccessReadOnly)) { return false; } if ($this->backendUser->isAdmin()) { return true; } if ($this->getSchema()?->hasCapability(TcaSchemaCapability::AccessAdminOnly)) { return false; } if (!$this->backendUser->check('tables_modify', $this->table)) { return false; } return !$this->isRecordLocked() && $this->hasPagePermission(Permission::PAGE_EDIT); } /** * Check if a page is locked */ protected function isRecordLocked(): bool { return (bool)$this->record[$this->getSchema()->getCapability(TcaSchemaCapability::EditLock)->getFieldName()]; } /** * Checks if the page is allowed to can be cut */ protected function canBeCut(): bool { if (!$this->languageAccess) { return false; } if ($this->getLanguageField() !== '' && !in_array($this->record[$this->getLanguageField()] ?? false, [0, -1]) ) { return false; } if (!$this->backendUser->check('tables_modify', $this->table)) { return false; } return !$this->isWebMount() && $this->canBeEdited() && !$this->isDeletePlaceholder(); } /** * Checks if the page is allowed to be copied */ protected function canBeCopied(): bool { if (!$this->languageAccess) { return false; } if ($this->getLanguageField() !== '' && !in_array($this->record[$this->getLanguageField()] ?? false, [0, -1]) ) { return false; } if (!$this->backendUser->check('tables_select', $this->table)) { return false; } return !$this->isRoot() && !$this->isWebMount() && !$this->isRecordInClipboard('copy') && $this->hasPagePermission(Permission::PAGE_SHOW) && !$this->isDeletePlaceholder(); } /** * Checks if something can be pasted into the node */ protected function canBePastedInto(): bool { if (!$this->languageAccess) { return false; } $clipboardElementCount = count($this->clipboard->elFromTable($this->table)); return $clipboardElementCount && $this->canBeCreated() && !$this->isDeletePlaceholder(); } /** * Checks if something can be pasted after the node */ protected function canBePastedAfter(): bool { if (!$this->languageAccess) { return false; } $clipboardElementCount = count($this->clipboard->elFromTable($this->table)); return $clipboardElementCount && $this->canBeCreated() && !$this->isDeletePlaceholder(); } /** * Check if sub pages of given page can be sorted */ protected function canBeSorted(): bool { if (!$this->languageAccess) { return false; } return $this->backendUser->check('tables_modify', $this->table) && $this->hasPagePermission(Permission::CONTENT_EDIT) && !$this->isDeletePlaceholder() && $this->backendUser->workspace === 0; } /** * Checks if the page is allowed to be removed */ protected function canBeDeleted(): bool { if (!$this->languageAccess) { return false; } return !$this->isRoot() && !$this->isDeletePlaceholder() && !$this->isRecordLocked() && !$this->isDeletionDisabledInTS() && $this->hasPagePermission(Permission::PAGE_DELETE); } /** * Checks if the page is allowed to be viewed in frontend */ protected function canBeViewed(): bool { return !$this->isRoot() && !$this->isDeleted() && $this->previewLinkCanBeBuild(); } /** * Checks if the page is allowed to show info */ protected function canShowInfo(): bool { return !$this->isRoot(); } /** * Checks if the user has clear cache rights */ protected function canClearCache(): bool { return !$this->isRoot() && ($this->backendUser->isAdmin() || ($this->backendUser->getTSConfig()['options.']['clearCache.']['pages'] ?? false)); } /** * Determines whether this node is deleted. */ protected function isDeleted(): bool { return !empty($this->record['deleted']) || $this->isDeletePlaceholder(); } /** * Returns true if current record is a root page */ protected function isRoot(): bool { return (int)$this->identifier === 0; } /** * Returns true if current record is a web mount */ protected function isWebMount(): bool { return in_array($this->identifier, $this->backendUser->getWebmounts()); } protected function getAdditionalAttributes(string $itemName): array { $attributes = []; if ($itemName === 'view' || $itemName === 'qrcode') { $attributes += $this->getViewAdditionalAttributes(); } if ($itemName === 'enable' || $itemName === 'disable') { $attributes += $this->getEnableDisableAdditionalAttributes(); } if ($itemName === 'delete') { $attributes += $this->getDeleteAdditionalAttributes(); } if ($itemName === 'pasteInto') { $attributes += $this->getPasteAdditionalAttributes('into'); } if ($itemName === 'pasteAfter') { $attributes += $this->getPasteAdditionalAttributes('after'); } if ($itemName === 'pagesSort') { $attributes += [ 'data-pages-sort-url' => (string)$this->uriBuilder->buildUriFromRoute('pages_sort', ['id' => $this->record['uid'] ?? null]), ]; } if ($itemName === 'pagesNewMultiple') { $attributes += [ 'data-pages-new-multiple-url' => (string)$this->uriBuilder->buildUriFromRoute('pages_new', ['id' => $this->record['uid'] ?? 0]), ]; } if ($itemName === 'edit') { $attributes = [ 'data-pages-language-uid' => $this->record[$this->getLanguageField()] ?? null, ]; } return $attributes; } protected function getPreviewPid(): int { return (int)($this->record[$this->getLanguageField()] ?? 0) === 0 ? (int)$this->record['uid'] : (int)$this->record['l10n_parent']; } /** * Returns the view link */ protected function getViewLink(): string { return (string)PreviewUriBuilder::create($this->record)->buildUri(); } /** * Checks if user has access to this column, the doktype * is not excluded and that it contains the given value. */ protected function canBeToggled(string $fieldName, int $value): bool { if (!$this->languageAccess || $this->isRoot()) { return false; } $field = $this->getSchema()->getField($fieldName); if ($field->supportsAccessControl() && !$this->isExcludedDoktype() && $this->backendUser->check('non_exclude_fields', $this->table . ':' . $fieldName) && $this->backendUser->check('tables_modify', $this->table) ) { return (int)$this->record[$fieldName] === $value; } return false; } /** * Returns true if a current user has access to the language of the record * * @see BackendUserAuthentication::checkLanguageAccess() */ protected function hasLanguageAccess(): bool { if ($this->backendUser->isAdmin()) { return true; } if (($languageField = $this->getLanguageField()) !== '' && isset($this->record[$languageField])) { return $this->backendUser->checkLanguageAccess((int)$this->record[$languageField]); } return true; } /** * Returns true if the page doktype is excluded */ protected function isExcludedDoktype(): bool { $doktypeRegistry = GeneralUtility::makeInstance(PageDoktypeRegistry::class); return !$doktypeRegistry->isPageTypeViewable((int)($this->record['doktype'] ?? 0)); } }