[!!!][FEATURE] Refactor and streamline click menu / context menu 24/50124/73
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Mon, 23 Jan 2017 13:49:26 +0000 (14:49 +0100)
committerBenni Mack <benni@typo3.org>
Tue, 7 Feb 2017 17:32:18 +0000 (18:32 +0100)
This change unifies the ClickMenu functionality of the pagetree (ExtJS)
with the ClickMenu code given in other areas of the TYPO3 Backend.

The following changes are made:
* Unify the naming, it's "ContextMenu" not "ClickMenu" anymore
* Configuration for record types are unified, the clickmenu shows
  the same entries in the same order in any place.
* ExtJS-based ContextMenu is removed, all based on the new
  ContextMenu functionality.
* A new way for extending the items inside the ContextMenu
  is handled via ItemProviders, which can easily be extended.
* Configuring clickmenu items is not done based on PageTS (as it
  was handled with the ExtJS pagetree), however certain items can
  be disabled via PageTS.

Resolves: #78192
Releases: master
Change-Id: I380ac73ced10fdc7b1fdec7261e2d56da3d7d938
Reviewed-on: https://review.typo3.org/50124
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
91 files changed:
Build/Resources/Public/Less/TYPO3/_element_contextmenu.less
typo3/sysext/backend/Classes/ClickMenu/ClickMenu.php [deleted file]
typo3/sysext/backend/Classes/ContextMenu/ContextMenu.php [new file with mode: 0644]
typo3/sysext/backend/Classes/ContextMenu/ContextMenuAction.php [deleted file]
typo3/sysext/backend/Classes/ContextMenu/ContextMenuActionCollection.php [deleted file]
typo3/sysext/backend/Classes/ContextMenu/ItemProviders/AbstractProvider.php [new file with mode: 0644]
typo3/sysext/backend/Classes/ContextMenu/ItemProviders/PageProvider.php [new file with mode: 0644]
typo3/sysext/backend/Classes/ContextMenu/ItemProviders/ProviderInterface.php [new file with mode: 0644]
typo3/sysext/backend/Classes/ContextMenu/ItemProviders/RecordProvider.php [new file with mode: 0644]
typo3/sysext/backend/Classes/ContextMenu/Pagetree/ContextMenuDataProvider.php [deleted file]
typo3/sysext/backend/Classes/ContextMenu/Pagetree/Extdirect/ContextMenuConfiguration.php [deleted file]
typo3/sysext/backend/Classes/Controller/BackendController.php
typo3/sysext/backend/Classes/Controller/ClickMenuController.php [deleted file]
typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php
typo3/sysext/backend/Classes/Controller/ContextMenuController.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/File/CreateFolderController.php
typo3/sysext/backend/Classes/Controller/File/FileUploadController.php
typo3/sysext/backend/Classes/Controller/File/RenameFileController.php
typo3/sysext/backend/Classes/Controller/File/ReplaceFileController.php
typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php
typo3/sysext/backend/Classes/Controller/NewRecordController.php
typo3/sysext/backend/Classes/Controller/PageLayoutController.php
typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php
typo3/sysext/backend/Classes/Template/Components/MetaInformation.php
typo3/sysext/backend/Classes/Template/DocumentTemplate.php
typo3/sysext/backend/Classes/Tree/View/FolderTreeView.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/backend/Classes/View/PageTreeView.php
typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
typo3/sysext/backend/Resources/Public/Css/backend.css
typo3/sysext/backend/Resources/Public/JavaScript/ClickMenu.js [deleted file]
typo3/sysext/backend/Resources/Public/JavaScript/ContextMenu.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js
typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js
typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/app.js
typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/contextmenu.js [deleted file]
typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/loadorder.txt
typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/nodeui.js
typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/tree.js
typo3/sysext/beuser/Classes/ContextMenu/ItemProvider.php [new file with mode: 0644]
typo3/sysext/beuser/Classes/Hook/BackendControllerHook.php [new file with mode: 0644]
typo3/sysext/beuser/Resources/Private/Layouts/Default.html
typo3/sysext/beuser/Resources/Private/Partials/BackendUser/IndexListRow.html
typo3/sysext/beuser/Resources/Private/Partials/BackendUser/OnlineListRow.html
typo3/sysext/beuser/Resources/Private/Partials/BackendUserGroup/IndexListRow.html
typo3/sysext/beuser/Resources/Private/Templates/BackendUser/Compare.html
typo3/sysext/beuser/Resources/Private/Templates/BackendUser/Index.html
typo3/sysext/beuser/Resources/Public/JavaScript/ContextMenuActions.js [new file with mode: 0644]
typo3/sysext/beuser/ext_localconf.php
typo3/sysext/beuser/ext_tables.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-78192-RefactorClickMenuContextMenu.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-78192-RefactorClickMenuContextMenu.rst [new file with mode: 0644]
typo3/sysext/core/Resources/Public/JavaScript/Contrib/extjs/ext-all-debug.js
typo3/sysext/core/Resources/Public/JavaScript/Contrib/extjs/ext-all.js
typo3/sysext/filelist/Classes/ContextMenu/ItemProviders/FileDragProvider.php [new file with mode: 0644]
typo3/sysext/filelist/Classes/ContextMenu/ItemProviders/FileProvider.php [new file with mode: 0644]
typo3/sysext/filelist/Classes/ContextMenu/ItemProviders/FileStorageProvider.php [new file with mode: 0644]
typo3/sysext/filelist/Classes/ContextMenu/ItemProviders/FilemountsProvider.php [new file with mode: 0644]
typo3/sysext/filelist/Classes/Controller/FileListController.php
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/filelist/Classes/Hook/BackendControllerHook.php [new file with mode: 0644]
typo3/sysext/filelist/Classes/ViewHelpers/Link/ClickMenuOnIconViewHelper.php
typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js [new file with mode: 0644]
typo3/sysext/filelist/ext_localconf.php [new file with mode: 0644]
typo3/sysext/filelist/ext_tables.php
typo3/sysext/fluid/Classes/ViewHelpers/Be/ContainerViewHelper.php
typo3/sysext/fluid/Classes/ViewHelpers/Be/PageInfoViewHelper.php
typo3/sysext/func/Classes/Controller/PageFunctionsController.php
typo3/sysext/impexp/Classes/Clickmenu.php [deleted file]
typo3/sysext/impexp/Classes/ContextMenu/ItemProvider.php [new file with mode: 0644]
typo3/sysext/impexp/Classes/Controller/ImportExportController.php
typo3/sysext/impexp/Classes/Hook/ContextMenuDisableItemsHook.php [deleted file]
typo3/sysext/impexp/Resources/Public/JavaScript/ContextMenuActions.js [new file with mode: 0644]
typo3/sysext/impexp/ext_tables.php
typo3/sysext/info/Classes/Controller/InfoModuleController.php
typo3/sysext/lang/Resources/Private/Language/locallang_misc.xlf
typo3/sysext/recordlist/Classes/RecordList.php
typo3/sysext/sys_action/Classes/ActionTask.php
typo3/sysext/sys_note/Resources/Private/Templates/Note/List.html
typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php
typo3/sysext/tstemplate/Resources/Private/Templates/InformationModule.html
typo3/sysext/version/Classes/ClickMenu/VersionClickMenu.php [deleted file]
typo3/sysext/version/Classes/ContextMenu/ItemProvider.php [new file with mode: 0644]
typo3/sysext/version/Classes/Controller/VersionModuleController.php
typo3/sysext/version/Resources/Public/JavaScript/ContextMenuActions.js [new file with mode: 0644]
typo3/sysext/version/ext_tables.php

index da7330d..5d75112 100644 (file)
@@ -15,6 +15,7 @@ div#contentMenu1 {
                min-width: 150px;
 
                &-item {
                min-width: 150px;
 
                &-item {
+                       cursor: pointer;
                        padding: 5px;
                        border-bottom-color: transparent;
                        border-top-color: transparent;
                        padding: 5px;
                        border-bottom-color: transparent;
                        border-top-color: transparent;
diff --git a/typo3/sysext/backend/Classes/ClickMenu/ClickMenu.php b/typo3/sysext/backend/Classes/ClickMenu/ClickMenu.php
deleted file mode 100644 (file)
index 440197e..0000000
+++ /dev/null
@@ -1,1609 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\ClickMenu;
-
-/*
- * 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!
- */
-
-use TYPO3\CMS\Backend\Clipboard\Clipboard;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Imaging\Icon;
-use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Resource\Folder;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
-use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
-use TYPO3\CMS\Core\Type\Bitmask\Permission;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Lang\LanguageService;
-
-/**
- * Class for generating the click menu
- * @internal
- */
-class ClickMenu
-{
-    /**
-     * Defines if the click menu is first level or second.
-     * Second means the click menu is triggered from another menu.
-     *
-     * @var int
-     */
-    public $cmLevel = 0;
-
-    /**
-     * Clipboard array (submitted by eg. pressing the paste button)
-     *
-     * @var bool
-     */
-    public $CB;
-
-    /**
-     * If set, the calling document should be in the listframe of a frameset.
-     *
-     * @var bool
-     */
-    public $listFrame = false;
-
-    /**
-     * If set, the menu is about database records, not files. (set if part 2 [1] of the item-var is NOT blank)
-     *
-     * @var bool
-     */
-    public $isDBmenu = false;
-
-    /**
-     * Stores the parts of the input $item string, splitted by "|":
-     * [0] = table/file, [1] = uid/blank, [2] = flag: If set, listFrame,
-     * If "2" then "content frame" is forced  [3] = ("+" prefix = disable
-     * all by default, enable these. Default is to disable) Items key list
-     *
-     * @var array
-     */
-    public $iParts = [];
-
-    /**
-     * Contains list of keywords of items to disable in the menu
-     *
-     * @var array
-     */
-    public $disabledItems = [];
-
-    /**
-     * Array of classes to be used for user processing of the menu content.
-     * This is for the API of adding items to the menu from outside.
-     *
-     * @var array
-     */
-    public $extClassArray = [];
-
-    /**
-     * Set, when edit icon is drawn.
-     *
-     * @var bool
-     */
-    public $editPageIconSet = false;
-
-    /**
-     * Set to TRUE, if editing of the element is OK.
-     *
-     * @var bool
-     */
-    public $editOK = false;
-
-    /**
-     * The current record
-     *
-     * @var array
-     */
-    public $rec = [];
-
-    /**
-     * Clipboard set from the outside
-     * Declared as public for now, should become protected
-     * soon-ish
-     * @var Clipboard;
-     */
-    public $clipObj;
-
-    /**
-     * The current page record
-     * @var array
-     */
-    protected $pageinfo;
-
-    /**
-     * Language Service property. Used to access localized labels
-     *
-     * @var LanguageService
-     */
-    protected $languageService;
-
-    /**
-     * @var BackendUserAuthentication
-     */
-    protected $backendUser;
-
-    /**
-     * @var IconFactory
-     */
-    protected $iconFactory;
-
-    /**
-     * @param LanguageService $languageService Language Service to inject
-     * @param BackendUserAuthentication $backendUser
-     */
-    public function __construct(LanguageService $languageService = null, BackendUserAuthentication $backendUser = null)
-    {
-        $this->languageService = $languageService ?: $GLOBALS['LANG'];
-        $this->backendUser = $backendUser ?: $GLOBALS['BE_USER'];
-        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-    }
-
-    /**
-     * Initialize click menu
-     *
-     * @return array the array to be returned as JSON including all click menu items
-     */
-    public function init()
-    {
-        $CMcontent = '';
-        // Setting GPvars:
-        $this->cmLevel = (int)GeneralUtility::_GP('cmLevel');
-        $this->CB = GeneralUtility::_GP('CB');
-
-        // Deal with Drag&Drop context menus
-        if ((string)GeneralUtility::_GP('dragDrop') !== '') {
-            return $this->printDragDropClickMenu(GeneralUtility::_GP('dragDrop'), GeneralUtility::_GP('srcId'), GeneralUtility::_GP('dstId'));
-        }
-        // Can be set differently as well
-        $this->iParts[0] = GeneralUtility::_GP('table');
-        $this->iParts[1] = GeneralUtility::_GP('uid');
-        $this->iParts[2] = GeneralUtility::_GP('listFr');
-        $this->iParts[3] = GeneralUtility::_GP('enDisItems');
-        // Setting flags:
-        if ($this->iParts[2]) {
-            $this->listFrame = true;
-        }
-        if (isset($this->iParts[1]) && $this->iParts[1] !== '') {
-            $this->isDBmenu = true;
-        }
-        $TSkey = ($this->isDBmenu ? 'page' : 'folder') . ($this->listFrame ? 'List' : 'Tree');
-        $this->disabledItems = GeneralUtility::trimExplode(',', $this->backendUser->getTSConfigVal('options.contextMenu.' . $TSkey . '.disableItems'), true);
-        // &cmLevel flag detected (2nd level menu)
-        if (!$this->cmLevel) {
-            // Make 1st level clickmenu:
-            if ($this->isDBmenu) {
-                $CMcontent = $this->printDBClickMenu($this->iParts[0], $this->iParts[1]);
-            } else {
-                $CMcontent = $this->printFileClickMenu($this->iParts[0]);
-            }
-        } else {
-            // Make 2nd level clickmenu (only for DBmenus)
-            if ($this->isDBmenu) {
-                $CMcontent = $this->printNewDBLevel($this->iParts[0], $this->iParts[1]);
-            }
-        }
-        // Return clickmenu content:
-        return $CMcontent;
-    }
-
-    /***************************************
-     *
-     * DATABASE
-     *
-     ***************************************/
-    /**
-     * Make 1st level clickmenu:
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array the array to be returned as JSON
-     */
-    public function printDBClickMenu($table, $uid)
-    {
-        $uid = (int)$uid;
-        // Get record:
-        $this->rec = BackendUtility::getRecordWSOL($table, $uid);
-        $menuItems = [];
-        $root = 0;
-        $DBmount = false;
-        // Rootlevel
-        if ($table === 'pages' && $uid === 0) {
-            $root = 1;
-        }
-        // DB mount
-        if ($table === 'pages' && in_array($uid, $this->backendUser->returnWebmounts())) {
-            $DBmount = true;
-        }
-        // Used to hide cut,copy icons for l10n-records
-        $l10nOverlay = false;
-        // Should only be performed for overlay-records within the same table
-        if (BackendUtility::isTableLocalizable($table) && $table !== 'pages_language_overlay') {
-            $l10nOverlay = (int)$this->rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0;
-        }
-        // If record found (or root), go ahead and fill the $menuItems array which will contain data for the elements to render.
-        if (is_array($this->rec) || $root) {
-            // Get permissions
-            $lCP = $this->backendUser->calcPerms(BackendUtility::getRecord('pages', $table === 'pages' ? (int)$this->rec['uid'] : (int)$this->rec['pid']));
-            // View
-            if (!in_array('view', $this->disabledItems, true)) {
-                if ($table === 'pages') {
-                    $menuItems['view'] = $this->DB_view($uid);
-                }
-                if ($table === 'tt_content') {
-                    $ws_rec = BackendUtility::getRecordWSOL($table, (int)$this->rec['uid']);
-                    $menuItems['view'] = $this->DB_view((int)$ws_rec['pid']);
-                }
-            }
-            // Edit:
-            if (!$root && ($this->backendUser->isPSet($lCP, $table, 'edit') || $this->backendUser->isPSet($lCP, $table, 'editcontent'))) {
-                if (!in_array('edit', $this->disabledItems, true)) {
-                    $menuItems['edit'] = $this->DB_edit($table, $uid);
-                }
-                $this->editOK = true;
-            }
-            // New:
-            if (!in_array('new', $this->disabledItems, true) && $this->backendUser->isPSet($lCP, $table, 'new')) {
-                $menuItems['new'] = $this->DB_new($table, $uid);
-            }
-            // Info:
-            if (!in_array('info', $this->disabledItems, true) && !$root) {
-                $menuItems['info'] = $this->DB_info($table, $uid);
-            }
-            $menuItems['spacer1'] = 'spacer';
-            // Copy:
-            if (!in_array('copy', $this->disabledItems, true) && !$root && !$DBmount && !$l10nOverlay) {
-                $menuItems['copy'] = $this->DB_copycut($table, $uid, 'copy');
-            }
-            // Cut:
-            if (!in_array('cut', $this->disabledItems, true) && !$root && !$DBmount && !$l10nOverlay && $this->editOK) {
-                $menuItems['cut'] = $this->DB_copycut($table, $uid, 'cut');
-            }
-            // Paste:
-            $elFromAllTables = count($this->clipObj->elFromTable(''));
-            if (!in_array('paste', $this->disabledItems, true) && $elFromAllTables) {
-                $selItem = $this->clipObj->getSelectedRecord();
-                $elInfo = [
-                    GeneralUtility::fixed_lgd_cs($selItem['_RECORD_TITLE'], $this->backendUser->uc['titleLen']),
-                    $root ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $this->rec), $this->backendUser->uc['titleLen']),
-                    $this->clipObj->currentMode()
-                ];
-                if ($table === 'pages' && $lCP & Permission::PAGE_NEW) {
-                    if ($elFromAllTables) {
-                        $menuItems['pasteinto'] = $this->DB_paste('', $uid, 'into', $elInfo);
-                    }
-                }
-                $elFromTable = count($this->clipObj->elFromTable($table));
-                if (!$root && !$DBmount && $elFromTable && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
-                    $menuItems['pasteafter'] = $this->DB_paste($table, -$uid, 'after', $elInfo);
-                }
-            }
-
-            // Delete:
-            $elInfo = [GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $this->rec), $this->backendUser->uc['titleLen'])];
-            $disableDeleteTS = $this->backendUser->getTSConfig('options.disableDelete');
-            $disableDelete = (bool) trim(isset($disableDeleteTS['properties'][$table]) ? $disableDeleteTS['properties'][$table] : $disableDeleteTS['value']);
-            if (!in_array('delete', $this->disabledItems, true) && !$root && !$DBmount && $this->backendUser->isPSet($lCP, $table, 'delete') && !$disableDelete) {
-                $menuItems['spacer2'] = 'spacer';
-                $menuItems['delete'] = $this->DB_delete($table, $uid, $elInfo);
-            }
-            if (!in_array('history', $this->disabledItems, true)) {
-                $menuItems['history'] = $this->DB_history($table, $uid);
-            }
-
-            $localItems = [];
-            if (!$this->cmLevel && !in_array('moreoptions', $this->disabledItems, true)) {
-                // Creating menu items here:
-                if ($this->editOK) {
-                    $localItems['spacer3'] = 'spacer';
-                    $localItems['moreoptions'] = $this->linkItem(
-                        $this->label('more') . '&nbsp;&nbsp;<span class="fa fa-caret-right"></span>',
-                        '<span class="fa fa-fw"></span>',
-                        'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript() . '&cmLevel=1&subname=moreoptions') . ');return false;',
-                        false,
-                        true
-                    );
-                    $menuItemHideUnhideAllowed = false;
-                    $hiddenField = '';
-                    // Check if column for disabled is defined
-                    if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
-                        $hiddenField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
-                        if (
-                            $hiddenField !== '' && !empty($GLOBALS['TCA'][$table]['columns'][$hiddenField]['exclude'])
-                            && $this->backendUser->check('non_exclude_fields', $table . ':' . $hiddenField)
-                        ) {
-                            $menuItemHideUnhideAllowed = true;
-                        }
-                    }
-                    if ($menuItemHideUnhideAllowed && !in_array('hide', $this->disabledItems, true)) {
-                        $localItems['hide'] = $this->DB_hideUnhide($table, $this->rec, $hiddenField);
-                    }
-                    $anyEnableColumnsFieldAllowed = false;
-                    // Check if columns are defined
-                    if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
-                        $columnsToCheck = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
-                        if ($table === 'pages' && !empty($columnsToCheck)) {
-                            $columnsToCheck[] = 'extendToSubpages';
-                        }
-                        foreach ($columnsToCheck as $currentColumn) {
-                            if (
-                                !empty($GLOBALS['TCA'][$table]['columns'][$currentColumn]['exclude'])
-                                && $this->backendUser->check('non_exclude_fields', $table . ':' . $currentColumn)
-                            ) {
-                                $anyEnableColumnsFieldAllowed = true;
-                            }
-                        }
-                    }
-                    if ($anyEnableColumnsFieldAllowed && !in_array('edit_access', $this->disabledItems, true)) {
-                        $localItems['edit_access'] = $this->DB_editAccess($table, $uid);
-                    }
-                }
-                // Find delete element among the input menu items and insert the local items just before that:
-                $c = 0;
-                $deleteFound = false;
-                foreach ($menuItems as $key => $value) {
-                    $c++;
-                    if ($key === 'delete') {
-                        $deleteFound = true;
-                        break;
-                    }
-                }
-                if ($deleteFound) {
-                    // .. subtract two... (delete item + its spacer element...)
-                    $c -= 2;
-                    // and insert the items just before the delete element.
-                    array_splice($menuItems, $c, 0, $localItems);
-                } else {
-                    $menuItems = array_merge($menuItems, $localItems);
-                }
-            }
-        }
-        // Adding external elements to the menuItems array
-        $menuItems = $this->processingByExtClassArray($menuItems, $table, $uid);
-        // Processing by external functions?
-        $menuItems = $this->externalProcessingOfDBMenuItems($menuItems);
-        if (!is_array($this->rec)) {
-            $this->rec = [];
-        }
-
-        // Return the printed elements:
-        return $this->printItems($menuItems);
-    }
-
-    /**
-     * Make 2nd level clickmenu (only for DBmenus)
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array the array to be returned as JSON
-     */
-    public function printNewDBLevel($table, $uid)
-    {
-        $localItems = [];
-        $uid = (int)$uid;
-        // Setting internal record to the table/uid :
-        $this->rec = BackendUtility::getRecordWSOL($table, $uid);
-        $menuItems = [];
-        $root = 0;
-        // Rootlevel
-        if ($table === 'pages' && $uid === 0) {
-            $root = 1;
-        }
-        // If record was found, check permissions and get menu items.
-        if (is_array($this->rec) || $root) {
-            $lCP = $this->backendUser->calcPerms(BackendUtility::getRecord('pages', $table === 'pages' ? (int)$this->rec['uid'] : (int)$this->rec['pid']));
-            // Edit:
-            if (!$root && ($this->backendUser->isPSet($lCP, $table, 'edit') || $this->backendUser->isPSet($lCP, $table, 'editcontent'))) {
-                $this->editOK = true;
-            }
-            $menuItems = $this->processingByExtClassArray($menuItems, $table, $uid);
-        }
-
-        $subname = GeneralUtility::_GP('subname');
-        if ($subname === 'moreoptions') {
-            // If the page can be edited, then show this:
-            if ($this->editOK) {
-                if (($table === 'pages' || $table === 'tt_content') && !in_array('move_wizard', $this->disabledItems, true)) {
-                    $localItems['move_wizard'] = $this->DB_moveWizard($table, $uid, $this->rec);
-                }
-                if (($table === 'pages' || $table === 'tt_content') && !in_array('new_wizard', $this->disabledItems, true)) {
-                    $localItems['new_wizard'] = $this->DB_newWizard($table, $uid, $this->rec);
-                }
-                if ($table === 'pages' && !in_array('perms', $this->disabledItems, true) && $this->backendUser->check('modules', 'system_BeuserTxPermission')) {
-                    $localItems['perms'] = $this->DB_perms($table, $uid, $this->rec);
-                }
-                if (!in_array('db_list', $this->disabledItems, true) && $this->backendUser->check('modules', 'web_list')) {
-                    $localItems['db_list'] = $this->DB_db_list($table, $uid, $this->rec);
-                }
-            }
-            // Temporary mount point item:
-            if ($table === 'pages') {
-                $localItems['temp_mount_point'] = $this->DB_tempMountPoint($uid);
-            }
-            // Merge the locally created items into the current menu items passed to this function.
-            $menuItems = array_merge($menuItems, $localItems);
-        }
-
-        // Return the printed elements:
-        if (!is_array($menuItems)) {
-            $menuItems = [];
-        }
-        return $this->printItems($menuItems);
-    }
-
-    /**
-     * Processing the $menuItems array (for extension classes) (DATABASE RECORDS)
-     *
-     * @param array $menuItems Array for manipulation.
-     * @return array Processed $menuItems array
-     */
-    public function externalProcessingOfDBMenuItems($menuItems)
-    {
-        return $menuItems;
-    }
-
-    /**
-     * Processing the $menuItems array by external classes (typ. adding items)
-     *
-     * @param array $menuItems Array for manipulation.
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Processed $menuItems array
-     */
-    public function processingByExtClassArray($menuItems, $table, $uid)
-    {
-        if (is_array($this->extClassArray)) {
-            foreach ($this->extClassArray as $conf) {
-                $obj = GeneralUtility::makeInstance($conf['name']);
-                $menuItems = $obj->main($this, $menuItems, $table, $uid);
-            }
-        }
-        return $menuItems;
-    }
-
-    /**
-     * Returning JavaScript for the onClick event linking to the input URL.
-     *
-     * @param string $url The URL relative to TYPO3_mainDir
-     * @param string $retUrl The return_url-parameter
-     * @param bool $hideCM If set, the "hideCM()" will be called
-     * @param string $overrideLoc If set, gives alternative location to load in (for example top frame or somewhere else)
-     * @return string JavaScript for an onClick event.
-     */
-    public function urlRefForCM($url, $retUrl = '', $hideCM = true, $overrideLoc = '')
-    {
-        $loc = 'top.list_frame';
-        return ($overrideLoc ? 'var docRef=' . $overrideLoc : 'var docRef=(top.list_frame)?top.list_frame:' . $loc)
-            . '; docRef.location.href=' . GeneralUtility::quoteJSvalue($url) . ($retUrl ? '+' . GeneralUtility::quoteJSvalue('&' . $retUrl . '=') . '+top.rawurlencode('
-            . $this->frameLocation('docRef.document') . '.pathname+' . $this->frameLocation('docRef.document') . '.search)' : '')
-            . ';';
-    }
-
-    /**
-     * Adding CM element for Clipboard "copy" and "cut"
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param string $type Type: "copy" or "cut
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_copycut($table, $uid, $type)
-    {
-        $isSel = '';
-        if ($this->clipObj->current === 'normal') {
-            $isSel = $this->clipObj->isSelected($table, $uid);
-        }
-        $addParam = [];
-        if ($this->listFrame) {
-            $addParam['reloadListFrame'] = 1;
-        }
-        $icon = $this->iconFactory->getIcon('actions-edit-' . $type . ($isSel === $type ? '-release' : ''), Icon::SIZE_SMALL)->render();
-        return $this->linkItem(
-            $this->label($type),
-            $icon,
-            'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $uid, ($type === 'copy' ? 1 : 0), ($isSel == $type), $addParam)) . ');return false;'
-        );
-    }
-
-    /**
-     * Adding CM element for Clipboard "paste into"/"paste after"
-     * NOTICE: $table and $uid should follow the special syntax for paste, see clipboard-class :: pasteUrl();
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record. NOTICE: Special syntax!
-     * @param string $type Type: "into" or "after
-     * @param array $elInfo Contains instructions about whether to copy or cut an element.
-     * @return array Item array, element in $menuItems
-     * @see \TYPO3\CMS\Backend\Clipboard\Clipboard::pasteUrl()
-     * @internal
-     */
-    public function DB_paste($table, $uid, $type, $elInfo)
-    {
-        $loc = 'top.list_frame';
-        $jsCode = $loc . '.location.href='
-            . GeneralUtility::quoteJSvalue($this->clipObj->pasteUrl($table, $uid, 0) . '&redirect=')
-            . ' + top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+'
-            . $this->frameLocation($loc . '.document') . '.search);';
-
-        if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
-            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
-            $lllKey = ($elInfo[2] === 'copy' ? 'copy' : 'move') . '_' . $type;
-            $confirmMessage = sprintf(
-                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.' . $lllKey),
-                $elInfo[0],
-                $elInfo[1]
-            );
-            $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
-                . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
-                . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
-                . $jsCode
-                . '} top.TYPO3.Modal.dismiss(); });';
-        }
-        $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
-        return $this->linkItem(
-            $this->label('paste' . $type),
-            $this->iconFactory->getIcon('actions-document-paste-' . $type, Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /**
-     * Adding CM element for Info
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_info($table, $uid)
-    {
-        return $this->linkItem(
-            $this->label('info'),
-            $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render(),
-            'top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . ');'
-        );
-    }
-
-    /**
-     * Adding CM element for History
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_history($table, $uid)
-    {
-        $url = BackendUtility::getModuleUrl('record_history', ['element' => $table . ':' . $uid]);
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_history')),
-            $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url, 'returnUrl')
-        );
-    }
-
-    /**
-     * Adding CM element for Permission setting
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param array $rec The "pages" record with "perms_*" fields inside.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_perms($table, $uid, $rec)
-    {
-        if (!ExtensionManagementUtility::isLoaded('beuser')) {
-            return '';
-        }
-
-        $parameters = [
-            'id' => $uid,
-        ];
-
-        if ($rec['perms_userid'] == $this->backendUser->user['uid'] || $this->backendUser->isAdmin()) {
-            $parameters['returnId'] = $uid;
-            $parameters['tx_beuser_system_beusertxpermission'] = ['action' => 'edit'];
-        }
-
-        $url = BackendUtility::getModuleUrl('system_BeuserTxPermission', $parameters);
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_perms')),
-            $this->iconFactory->getIcon('status-status-locked', Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url)
-        );
-    }
-
-    /**
-     * Adding CM element for DBlist
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param array $rec Record of the element (needs "pid" field if not pages-record)
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_db_list($table, $uid, $rec)
-    {
-        $urlParams = [];
-        $urlParams['id'] = $table === 'pages' ? $uid : $rec['pid'];
-        $urlParams['table'] = $table === 'pages' ? '' : $table;
-        $url = BackendUtility::getModuleUrl('web_list', $urlParams);
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_db_list')),
-            $this->iconFactory->getIcon('actions-system-list-open', Icon::SIZE_SMALL)->render(),
-            'top.nextLoadModuleUrl=' . GeneralUtility::quoteJSvalue($url) . ';top.goToModule(\'web_list\', 1);'
-        );
-    }
-
-    /**
-     * Adding CM element for Moving wizard
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param array $rec Record. Needed for tt-content elements which will have the sys_language_uid sent
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_moveWizard($table, $uid, $rec)
-    {
-        // Hardcoded field for tt_content elements.
-        $url = BackendUtility::getModuleUrl('move_element') . '&table=' . $table . '&uid=' . $uid;
-        $url .= ($table === 'tt_content' ? '&sys_language_uid=' . (int)$rec['sys_language_uid'] : '');
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_moveWizard' . ($table === 'pages' ? '_page' : ''))),
-            $this->iconFactory->getIcon('actions-' . ($table === 'pages' ? 'page' : 'document') . '-move', Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url, 'returnUrl')
-        );
-    }
-
-    /**
-     * Adding CM element for Create new wizard (either BackendUtility::getModuleUrl('db_new') or BackendUtility::getModuleUrl('new_content_element') or custom wizard)
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param array $rec Record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_newWizard($table, $uid, $rec)
-    {
-        if ($table === 'pages') {
-            $url = BackendUtility::getModuleUrl('db_new', ['id' => $uid, 'pagesOnly' => 1]);
-        } else {
-            //  If mod.newContentElementWizard.override is set, use a custom module instead
-            $tsConfig = BackendUtility::getModTSconfig((int)$this->pageinfo['uid'], 'mod');
-            $newContentWizardModuleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
-                ? $tsConfig['properties']['newContentElementWizard.']['override']
-                : 'new_content_element';
-            $url = BackendUtility::getModuleUrl($newContentWizardModuleName, ['id' => $rec['pid'], 'sys_language_uid' => (int)$rec['sys_language_uid']]);
-        }
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_newWizard')),
-            $this->iconFactory->getIcon(($table === 'pages' ? 'actions-page-new' : 'actions-document-new'), Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url, 'returnUrl')
-        );
-    }
-
-    /**
-     * Adding CM element for Editing of the access related fields of a table (disable, starttime, endtime, fe_groups)
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_editAccess($table, $uid)
-    {
-        $url = BackendUtility::getModuleUrl('record_edit', [
-            'columnsOnly' => (implode(',', $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']) . ($table === 'pages' ? ',extendToSubpages' : '')),
-            'edit[' . $table . '][' . $uid . ']' => 'edit'
-        ]);
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_editAccess')),
-            $this->iconFactory->getIcon('actions-document-edit-access', Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url, 'returnUrl'),
-            1
-        );
-    }
-
-    /**
-     * Adding CM element for edit page properties
-     *
-     * @param int $uid page uid to edit (PID)
-     * @return array Item array, element in $menuItems
-     * @internal
-     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
-     */
-    public function DB_editPageProperties($uid)
-    {
-        GeneralUtility::logDeprecatedFunction();
-        $url = BackendUtility::getModuleUrl('record_edit', [
-            'edit[pages][' . $uid . ']' => 'edit'
-        ]);
-        return $this->linkItem(
-            htmlspecialchars($this->languageService->getLL('CM_editPageProperties')),
-            $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render(),
-            $this->urlRefForCM($url, 'returnUrl'),
-            1
-        );
-    }
-
-    /**
-     * Adding CM element for regular editing of the element!
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_edit($table, $uid)
-    {
-        // If another module was specified, replace the default Page module with the new one
-        $newPageModule = trim($this->backendUser->getTSConfigVal('options.overridePageModule'));
-        $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
-        $loc = 'top.list_frame';
-        $iconName = 'actions-open';
-        if ($table === 'pages') {
-            $iconName = 'actions-page-open';
-        }
-        $theIcon = $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render();
-
-        $link = BackendUtility::getModuleUrl('record_edit', [
-            'edit[' . $table . '][' . $uid . ']' => 'edit'
-        ]);
-
-        if ($this->iParts[0] === 'pages' && $this->iParts[1] && $this->backendUser->check('modules', $pageModule)) {
-            $this->editPageIconSet = true;
-        }
-        $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' . GeneralUtility::quoteJSvalue($link . '&returnUrl=') . '+top.rawurlencode(' . $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search);}';
-        return $this->linkItem($this->label('edit'), $theIcon, $editOnClick . ';');
-    }
-
-    /**
-     * Adding CM element for regular Create new element
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_new($table, $uid)
-    {
-        $frame = 'top.list_frame';
-        $location = $this->frameLocation($frame . '.document');
-        $module = $this->listFrame
-            ? GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('record_edit', ['edit[' . $table . '][-' . $uid . ']' => 'new']) . '&returnUrl=') . '+top.rawurlencode(' . $location . '.pathname+' . $location . '.search)'
-            : GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => (int)$uid]));
-        $editOnClick = 'if(' . $frame . '){' . $frame . '.location.href=' . $module . ';}';
-        $icon = $this->iconFactory->getIcon('actions-' . ($table === 'pages' ? 'page' : 'document') . '-new', Icon::SIZE_SMALL)->render();
-        return $this->linkItem($this->label('new'), $icon, $editOnClick);
-    }
-
-    /**
-     * Adding CM element for Delete
-     *
-     * @param string $table Table name
-     * @param int $uid UID for the current record.
-     * @param array $elInfo Label for including in the confirmation message, EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_delete($table, $uid, $elInfo)
-    {
-        $loc = 'top.list_frame';
-        $jsCode = $loc . '.location.href='
-            . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=')
-            . '+top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+'
-            . $this->frameLocation($loc . '.document') . '.search)+'
-            . GeneralUtility::quoteJSvalue(
-                '&cmd[' . $table . '][' . $uid . '][delete]=1&prErr=1'
-            )
-            . ';';
-
-        if ($table === 'pages') {
-            $jsCode .= 'if (top && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer.PageTree) {';
-            $jsCode .= '     top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);';
-            $jsCode .= '}';
-        }
-
-        if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
-            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
-            $confirmMessage = sprintf(
-                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'),
-                $elInfo[0]
-            );
-            $confirmMessage .= BackendUtility::referenceCount(
-                $table,
-                $uid,
-                ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord')
-            );
-            $confirmMessage .= BackendUtility::translationCount(
-                $table,
-                $uid,
-                ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
-            );
-            $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
-                . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
-                . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
-                . $jsCode
-                . '} top.TYPO3.Modal.dismiss(); });';
-        }
-        $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
-        return $this->linkItem(
-            $this->label('delete'),
-            $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /**
-     * Adding CM element for View Page
-     *
-     * @param int $id Page uid (PID)
-     * @param string $anchor Anchor, if any
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_view($id, $anchor = '')
-    {
-        $icon = $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render();
-        return $this->linkItem($this->label('view'), $icon, BackendUtility::viewOnClick($id, '', null, $anchor) . ';');
-    }
-
-    /**
-     * Adding element for setting temporary mount point.
-     *
-     * @param int $page_id Page uid (PID)
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_tempMountPoint($page_id)
-    {
-        return $this->linkItem(
-            $this->label('tempMountPoint'),
-            $this->iconFactory->getIcon('actions-pagetree-mountroot', Icon::SIZE_SMALL)->render(),
-            'if (top.nav_frame) {
-                               var node = top.TYPO3.Backend.NavigationContainer.PageTree.getSelected();
-                               if (node === null) {
-                                       return false;
-                               }
-
-                               var useNode = {
-                                       attributes: {
-                                               nodeData: {
-                                                       id: ' . (int)$page_id . '
-                                               }
-                                       }
-                               };
-
-                               node.ownerTree.commandProvider.mountAsTreeRoot(useNode, node.ownerTree);
-                       }
-                       ');
-    }
-
-    /**
-     * Adding CM element for hide/unhide of the input record
-     *
-     * @param string $table Table name
-     * @param array $rec Record array
-     * @param string $hideField Name of the hide field
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function DB_hideUnhide($table, $rec, $hideField)
-    {
-        return $this->DB_changeFlag($table, $rec, $hideField, $this->label(($rec[$hideField] ? 'un' : '') . 'hide'));
-    }
-
-    /**
-     * Adding CM element for a flag field of the input record
-     *
-     * @param string $table Table name
-     * @param array $rec Record array
-     * @param string $flagField Name of the flag field
-     * @param string $title Menu item Title
-     * @return array Item array, element in $menuItems
-     */
-    public function DB_changeFlag($table, $rec, $flagField, $title)
-    {
-        $uid = $rec['_ORIG_uid'] ?: $rec['uid'];
-        $loc = 'top.list_frame';
-        $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' .
-            GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=') . '+top.rawurlencode(' .
-            $this->frameLocation($loc . '.document') . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
-            GeneralUtility::quoteJSvalue(
-                '&data[' . $table . '][' . $uid . '][' . $flagField . ']=' . ($rec[$flagField] ? 0 : 1) . '&prErr=1'
-            ) . ';};';
-        if ($table === 'pages') {
-            $editOnClick .= 'top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);';
-        }
-        return $this->linkItem(
-            $title,
-            $this->iconFactory->getIcon('actions-edit-' . ($rec[$flagField] ? 'un' : '') . 'hide', Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;',
-            1
-        );
-    }
-
-    /***************************************
-     *
-     * FILE
-     *
-     ***************************************/
-    /**
-     * Make 1st level clickmenu:
-     *
-     * @param string $combinedIdentifier The combined identifier
-     * @return string HTML content
-     * @see \TYPO3\CMS\Core\Resource\ResourceFactory::retrieveFileOrFolderObject()
-     */
-    public function printFileClickMenu($combinedIdentifier)
-    {
-        $identifier = '';
-        $menuItems = [];
-        $combinedIdentifier = rawurldecode($combinedIdentifier);
-        $fileObject = ResourceFactory::getInstance()
-                ->retrieveFileOrFolderObject($combinedIdentifier);
-        if ($fileObject) {
-            $folder = false;
-            $isStorageRoot = false;
-            $isOnline = true;
-            $userMayViewStorage = false;
-            $userMayEditStorage = false;
-            $identifier = $fileObject->getCombinedIdentifier();
-            if ($fileObject instanceof Folder) {
-                $folder = true;
-                if ($fileObject->getIdentifier() === $fileObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
-                    $isStorageRoot = true;
-                    if ($this->backendUser->check('tables_select', 'sys_file_storage')) {
-                        $userMayViewStorage = true;
-                    }
-                    if ($this->backendUser->check('tables_modify', 'sys_file_storage')) {
-                        $userMayEditStorage = true;
-                    }
-                }
-                if (!$fileObject->getStorage()->isOnline()) {
-                    $isOnline = false;
-                }
-            }
-            // Hide
-            if (!in_array('hide', $this->disabledItems, true) && $isStorageRoot && $userMayEditStorage) {
-                $record = BackendUtility::getRecord('sys_file_storage', $fileObject->getStorage()->getUid());
-                $menuItems['hide'] = $this->DB_changeFlag(
-                    'sys_file_storage',
-                    $record,
-                    'is_online',
-                    $this->label($record['is_online'] ? 'offline' : 'online')
-                );
-            }
-            // Edit
-            if (!in_array('edit', $this->disabledItems, true) && $fileObject->checkActionPermission('write')) {
-                if (!$folder && !$isStorageRoot && $fileObject->isIndexed() && $this->backendUser->check('tables_modify', 'sys_file_metadata')) {
-                    $metaData = $fileObject->_getMetaData();
-                    $menuItems['edit2'] = $this->DB_edit('sys_file_metadata', $metaData['uid']);
-                }
-                if (!$folder && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $fileObject->getExtension()) && $fileObject->checkActionPermission('write')) {
-                    $menuItems['edit'] = $this->FILE_launch($identifier, 'file_edit', 'editcontent', 'actions-page-open');
-                } elseif ($isStorageRoot && $userMayEditStorage) {
-                    $menuItems['edit'] = $this->DB_edit('sys_file_storage', $fileObject->getStorage()->getUid());
-                }
-            }
-            // Rename
-            if (!in_array('rename', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('rename')) {
-                $menuItems['rename'] = $this->FILE_launch($identifier, 'file_rename', 'rename', 'actions-edit-rename');
-            }
-            // Upload
-            if (!in_array('upload', $this->disabledItems, true) && $folder && $isOnline && $fileObject->checkActionPermission('write')) {
-                $menuItems['upload'] = $this->FILE_launch($identifier, 'file_upload', 'upload', 'actions-edit-upload');
-            }
-            // New
-            if (!in_array('new', $this->disabledItems, true) && $folder && $isOnline && $fileObject->checkActionPermission('write')) {
-                $menuItems['new'] = $this->FILE_launch($identifier, 'file_newfolder', 'new', 'actions-document-new');
-            }
-            // Info
-            if (!in_array('info', $this->disabledItems, true) && $fileObject->checkActionPermission('read')) {
-                if ($isStorageRoot && $userMayViewStorage) {
-                    $menuItems['info'] = $this->DB_info('sys_file_storage', $fileObject->getStorage()->getUid());
-                } elseif (!$folder) {
-                    $menuItems['info'] = $this->fileInfo($identifier);
-                }
-            }
-            $menuItems[] = 'spacer';
-            // Copy:
-            if (!in_array('copy', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('read')) {
-                $menuItems['copy'] = $this->FILE_copycut($identifier, 'copy');
-            }
-            // Cut:
-            if (!in_array('cut', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('move')) {
-                $menuItems['cut'] = $this->FILE_copycut($identifier, 'cut');
-            }
-            // Paste:
-            $elFromAllTables = count($this->clipObj->elFromTable('_FILE'));
-            if (!in_array('paste', $this->disabledItems, true) && $elFromAllTables && $folder && $fileObject->checkActionPermission('write')) {
-                $elArr = $this->clipObj->elFromTable('_FILE');
-                $selItem = reset($elArr);
-                $clickedFileOrFolder = ResourceFactory::getInstance()->retrieveFileOrFolderObject($combinedIdentifier);
-                $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem);
-                $elInfo = [
-                    $fileOrFolderInClipBoard->getName(),
-                    $clickedFileOrFolder->getName(),
-                    $this->clipObj->currentMode()
-                ];
-                if (!$fileOrFolderInClipBoard instanceof Folder || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $clickedFileOrFolder)) {
-                    $menuItems['pasteinto'] = $this->FILE_paste($identifier, $selItem, $elInfo);
-                }
-            }
-            $menuItems[] = 'spacer';
-            // Delete:
-            if (!in_array('delete', $this->disabledItems, true) && $fileObject->checkActionPermission('delete')) {
-                if ($isStorageRoot && $userMayEditStorage) {
-                    $elInfo = [GeneralUtility::fixed_lgd_cs($fileObject->getStorage()->getName(), $this->backendUser->uc['titleLen'])];
-                    $menuItems['delete'] = $this->DB_delete('sys_file_storage', $fileObject->getStorage()->getUid(), $elInfo);
-                } elseif (!$isStorageRoot) {
-                    $menuItems['delete'] = $this->FILE_delete($identifier);
-                }
-            }
-        }
-        // Adding external elements to the menuItems array
-        $menuItems = $this->processingByExtClassArray($menuItems, $identifier, 0);
-        // Processing by external functions?
-        $menuItems = $this->externalProcessingOfFileMenuItems($menuItems);
-        // Return the printed elements:
-        return $this->printItems($menuItems);
-    }
-
-    /**
-     * Processing the $menuItems array (for extension classes) (FILES)
-     *
-     * @param array $menuItems Array for manipulation.
-     * @return array Processed $menuItems array
-     */
-    public function externalProcessingOfFileMenuItems($menuItems)
-    {
-        return $menuItems;
-    }
-
-    /**
-     * Multi-function for adding an entry to the $menuItems array
-     *
-     * @param string $path Path to the file/directory (target)
-     * @param string $moduleName Script (deprecated) or module name (e.g. file_edit) to pass &target= to
-     * @param string $type "type" is the code which fetches the correct label for the element from "cm.
-     * @param string $iconName
-     * @param bool $noReturnUrl If set, the return URL parameter will not be set in the link
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function FILE_launch($path, $moduleName, $type, $iconName, $noReturnUrl = false)
-    {
-        $loc = 'top.list_frame';
-        $scriptUrl = BackendUtility::getModuleUrl($moduleName);
-
-        $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' . GeneralUtility::quoteJSvalue($scriptUrl . '&target=' . rawurlencode($path)) . ($noReturnUrl ? '' : '+\'&returnUrl=\'+top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+' . $this->frameLocation($loc . '.document') . '.search)') . ';}';
-        return $this->linkItem(
-            $this->label($type),
-            $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render(),
-            $editOnClick
-        );
-    }
-
-    /**
-     * Returns element for copy or cut of files.
-     *
-     * @param string $path Path to the file/directory (target)
-     * @param string $type Type: "copy" or "cut
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function FILE_copycut($path, $type)
-    {
-        $isSel = '';
-        // Pseudo table name for use in the clipboard.
-        $table = '_FILE';
-        $uid = GeneralUtility::shortMD5($path);
-        if ($this->clipObj->current === 'normal') {
-            $isSel = $this->clipObj->isSelected($table, $uid);
-        }
-        $addParam = [];
-        if ($this->listFrame) {
-            $addParam['reloadListFrame'] = 1;
-        }
-        return $this->linkItem(
-            $this->label($type),
-            $this->iconFactory->getIcon('actions-edit-' . $type . ($isSel === $type ? '-release' : ''), Icon::SIZE_SMALL)->render(),
-            'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlFile($path, ($type === 'copy' ? 1 : 0), ($isSel == $type), $addParam)) . ');return false;'
-        );
-    }
-
-    /**
-     * Creates element for deleting of target
-     *
-     * @param string $path Path to the file/directory (target)
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function FILE_delete($path)
-    {
-        $loc = 'top.list_frame';
-        $jsCode = $loc . '.location.href='
-            . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_file') . '&redirect=')
-            . '+top.rawurlencode(' . $this->frameLocation(($loc . '.document'))
-            . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
-            GeneralUtility::quoteJSvalue(
-                '&file[delete][0][data]=' . rawurlencode($path)
-            );
-        if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
-            $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($path);
-            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
-            $confirmMessage = sprintf(
-                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'),
-                $fileOrFolderObject->getName()
-            );
-            if ($fileOrFolderObject instanceof Folder) {
-                $confirmMessage .= BackendUtility::referenceCount('_FILE', $fileOrFolderObject->getIdentifier(), ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder'));
-            } else {
-                $confirmMessage .= BackendUtility::referenceCount('sys_file', $fileOrFolderObject->getUid(), ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile'));
-            }
-            $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
-                . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
-                . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
-                . $jsCode
-                . '} top.TYPO3.Modal.dismiss(); });';
-        }
-        $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
-        return $this->linkItem(
-            $this->label('delete'),
-            $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /**
-     * Creates element for pasting files.
-     *
-     * @param string $path Path to the file/directory (target)
-     * @param string $target target - NOT USED.
-     * @param array $elInfo Various values for the labels.
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function FILE_paste($path, $target, $elInfo)
-    {
-        $loc = 'top.list_frame';
-
-        $jsCode = $loc . '.location.href='
-            . GeneralUtility::quoteJSvalue($this->clipObj->pasteUrl('_FILE', $path, 0) . '&redirect=')
-            . '+top.rawurlencode(' . $this->frameLocation($loc . '.document')
-            . '.pathname+' . $this->frameLocation($loc . '.document') . '.search);';
-
-        if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
-            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
-
-            $confirmMessage = sprintf(
-                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.'
-                    . ($elInfo[2] === 'copy' ? 'copy' : 'move') . '_into'),
-                $elInfo[0],
-                $elInfo[1]
-            );
-
-            $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
-                . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
-                . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
-                . $jsCode
-                . '} top.TYPO3.Modal.dismiss(); });';
-        }
-        $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
-        return $this->linkItem(
-            $this->label('pasteinto'),
-            $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /**
-     * Adding ClickMenu element for file info
-     *
-     * @param string $identifier The combined identifier of the file.
-     * @return array Item array, element in $menuItems
-     */
-    protected function fileInfo($identifier)
-    {
-        return $this->DB_info('_FILE', $identifier);
-    }
-
-    /***************************************
-     *
-     * DRAG AND DROP
-     *
-     ***************************************/
-    /**
-     * Make 1st level clickmenu:
-     *
-     * @param string $table The absolute path
-     * @param int $srcId UID for the current record.
-     * @param int $dstId Destination ID
-     * @return array the array to be returned as JSON
-     */
-    public function printDragDropClickMenu($table, $srcId, $dstId)
-    {
-        $menuItems = [];
-        // If the drag and drop menu should apply to PAGES use this set of menu items
-        if ($table === 'pages') {
-            // Move Into:
-            $menuItems['movePage_into'] = $this->dragDrop_copymovepage($srcId, $dstId, 'move', 'into');
-            // Move After:
-            $menuItems['movePage_after'] = $this->dragDrop_copymovepage($srcId, $dstId, 'move', 'after');
-            // Copy Into:
-            $menuItems['copyPage_into'] = $this->dragDrop_copymovepage($srcId, $dstId, 'copy', 'into');
-            // Copy After:
-            $menuItems['copyPage_after'] = $this->dragDrop_copymovepage($srcId, $dstId, 'copy', 'after');
-        }
-        // If the drag and drop menu should apply to FOLDERS use this set of menu items
-        if ($table === 'folders') {
-            // Move Into:
-            $menuItems['moveFolder_into'] = $this->dragDrop_copymovefolder($srcId, $dstId, 'move');
-            // Copy Into:
-            $menuItems['copyFolder_into'] = $this->dragDrop_copymovefolder($srcId, $dstId, 'copy');
-        }
-        // Adding external elements to the menuItems array
-        $menuItems = $this->processingByExtClassArray($menuItems, 'dragDrop_' . $table, $srcId);
-        // to extend this, you need to apply a Context Menu to a "virtual" table called "dragDrop_pages" or similar
-        // Processing by external functions?
-        $menuItems = $this->externalProcessingOfDBMenuItems($menuItems);
-        // Return the printed elements:
-        return $this->printItems($menuItems);
-    }
-
-    /**
-     * Processing the $menuItems array (for extension classes) (DRAG'N DROP)
-     *
-     * @param array $menuItems Array for manipulation.
-     * @return array Processed $menuItems array
-     */
-    public function externalProcessingOfDragDropMenuItems($menuItems)
-    {
-        return $menuItems;
-    }
-
-    /**
-     * Adding CM element for Copying/Moving a Page Into/After from a drag & drop action
-     *
-     * @param int $srcUid source UID code for the record to modify
-     * @param int $dstUid destination UID code for the record to modify
-     * @param string $action Action code: either "move" or "copy
-     * @param string $into Parameter code: either "into" or "after
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function dragDrop_copymovepage($srcUid, $dstUid, $action, $into)
-    {
-        $negativeSign = $into === 'into' ? '' : '-';
-        $loc = 'top.list_frame';
-        $editOnClick = 'if(' . $loc . '){' . $loc . '.document.location=' .
-            GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=') . '+top.rawurlencode(' .
-            $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
-            GeneralUtility::quoteJSvalue(
-                '&cmd[pages][' . $srcUid . '][' . $action . ']=' . $negativeSign . $dstUid . '&prErr=1'
-            ) . ';};';
-        return $this->linkItem(
-            $this->label($action . 'Page_' . $into),
-            $this->iconFactory->getIcon('actions-document-paste-' . $into, Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /**
-     * Adding CM element for Copying/Moving a Folder Into from a drag & drop action
-     *
-     * @param string $srcPath source path for the record to modify
-     * @param string $dstPath destination path for the records to modify
-     * @param string $action Action code: either "move" or "copy
-     * @return array Item array, element in $menuItems
-     * @internal
-     */
-    public function dragDrop_copymovefolder($srcPath, $dstPath, $action)
-    {
-        $loc = 'top.list_frame';
-        $editOnClick = 'if(' . $loc . '){' . $loc . '.document.location=' .
-            GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_file') . '&redirect=') . '+top.rawurlencode(' .
-            $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
-            GeneralUtility::quoteJSvalue(
-                '&file[' . $action . '][0][data]=' . $srcPath . '&file[' . $action . '][0][target]=' . $dstPath . '&prErr=1'
-            ) . ';};';
-        return $this->linkItem(
-            $this->label($action . 'Folder_into'),
-            $this->iconFactory->getIcon('apps-pagetree-drag-move-into', Icon::SIZE_SMALL)->render(),
-            $editOnClick . 'return false;'
-        );
-    }
-
-    /***************************************
-     *
-     * COMMON
-     *
-     **************************************/
-    /**
-     * Prints the items from input $menuItems array - as JS section for writing to the div-layers.
-     *
-     * @param array $menuItems Array
-     * @return array the array to be returned via JSON
-     */
-    public function printItems($menuItems)
-    {
-        // Enable/Disable items
-        $menuItems = $this->enableDisableItems($menuItems);
-        // Clean up spacers
-        $menuItems = $this->cleanUpSpacers($menuItems);
-        // Adding JS part and return the content
-        return $this->printLayerJScode($menuItems);
-    }
-
-    /**
-     * Create the JavaScript section
-     *
-     * @param array $menuItems The $menuItems array to print
-     * @return array|null the items to print, and The JavaScript section which will print the content of the CM to the div-layer in the target frame.
-     */
-    public function printLayerJScode($menuItems)
-    {
-        // Clipboard must not be submitted - then it's probably a copy/cut situation.
-        if ($this->isCMlayers()) {
-            // Create the table displayed in the clickmenu layer:
-            // Wrap the inner table in another table to create outer border:
-            return [
-                'items' => $this->menuItemsForClickMenu($menuItems),
-                'level' => $this->cmLevel
-            ];
-        }
-    }
-
-    /**
-     * Traverses the menuItems and generates an output array for implosion in the CM div-layers table.
-     *
-     * @param array $menuItems Array
-     * @return array array for implosion in the CM div-layers table.
-     */
-    public function menuItemsForClickMenu($menuItems)
-    {
-        $out = [];
-        foreach ($menuItems as $cc => $i) {
-            // MAKE horizontal spacer
-            if (is_string($i) && $i === 'spacer') {
-                $out[] = '<span class="list-group-item list-group-item-divider"></span>';
-            } else {
-                // Just make normal element:
-                $onClick = $i[3];
-                $onClick = preg_replace('/return[[:space:]]+hideCM\\(\\)[[:space:]]*;/i', '', $onClick);
-                $onClick = preg_replace('/return[[:space:]]+false[[:space:]]*;/i', '', $onClick);
-                $onClick = preg_replace('/hideCM\\(\\);/i', '', $onClick);
-                if (!$i[5]) {
-                    $onClick .= 'TYPO3.ClickMenu.hideAll();';
-                }
-                $out[] = '
-                                       <a href="javascript:;" class="list-group-item" onclick="' . htmlspecialchars($onClick) . '">
-                                               <span class="list-group-item-icon">' . $i[2] . '</span> ' . $i[1] . '
-                                       </a>';
-            }
-        }
-        return $out;
-    }
-
-    /**
-     * Adds or inserts a menu item
-     * Can be used to set the position of new menu entries within the list of existing menu entries. Has this syntax: [cmd]:[menu entry key],[cmd].... cmd can be "after", "before" or "top" (or blank/"bottom" which is default). If "after"/"before" then menu items will be inserted after/before the existing entry with [menu entry key] if found. "after-spacer" and "before-spacer" do the same, but inserts before or after an item and a spacer. If not found, the bottom of list. If "top" the items are inserted in the top of the list.
-     *
-     * @param array $menuItems Menu items array
-     * @param array $newMenuItems Menu items array to insert
-     * @param string $position Position command string. Has this syntax: [cmd]:[menu entry key],[cmd].... cmd can be "after", "before" or "top" (or blank/"bottom" which is default). If "after"/"before" then menu items will be inserted after/before the existing entry with [menu entry key] if found. "after-spacer" and "before-spacer" do the same, but inserts before or after an item and a spacer. If not found, the bottom of list. If "top" the items are inserted in the top of the list.
-     * @return array Menu items array, processed.
-     */
-    public function addMenuItems($menuItems, $newMenuItems, $position = '')
-    {
-        if (is_array($newMenuItems)) {
-            $pointer = 0;
-            if ($position) {
-                $posArr = GeneralUtility::trimExplode(',', $position, true);
-                foreach ($posArr as $pos) {
-                    list($place, $menuEntry) = GeneralUtility::trimExplode(':', $pos, true);
-                    list($place, $placeExtra) = GeneralUtility::trimExplode('-', $place, true);
-                    // Bottom
-                    $pointer = count($menuItems);
-                    $found = false;
-                    if ($place) {
-                        switch (strtolower($place)) {
-                            case 'after':
-                            case 'before':
-                                if ($menuEntry) {
-                                    $p = 1;
-                                    reset($menuItems);
-                                    while (true) {
-                                        if ((string)key($menuItems) === $menuEntry) {
-                                            $pointer = $p;
-                                            $found = true;
-                                            break;
-                                        }
-                                        if (!next($menuItems)) {
-                                            break;
-                                        }
-                                        $p++;
-                                    }
-                                    if (!$found) {
-                                        break;
-                                    }
-                                    if ($place === 'before') {
-                                        $pointer--;
-                                        if ($placeExtra === 'spacer' and prev($menuItems) === 'spacer') {
-                                            $pointer--;
-                                        }
-                                    } elseif ($place === 'after') {
-                                        if ($placeExtra === 'spacer' and next($menuItems) === 'spacer') {
-                                            $pointer++;
-                                        }
-                                    }
-                                }
-                                break;
-                            default:
-                                if (strtolower($place) === 'top') {
-                                    $pointer = 0;
-                                } else {
-                                    $pointer = count($menuItems);
-                                }
-                                $found = true;
-                        }
-                    }
-                    if ($found) {
-                        break;
-                    }
-                }
-            }
-            $pointer = max(0, $pointer);
-            $menuItemsBefore = array_slice($menuItems, 0, $pointer ?: 0);
-            $menuItemsAfter = array_slice($menuItems, $pointer);
-            $menuItems = $menuItemsBefore + $newMenuItems + $menuItemsAfter;
-        }
-        return $menuItems;
-    }
-
-    /**
-     * Creating an array with various elements for the clickmenu entry
-     *
-     * @param string $str The label, htmlspecialchar'ed already
-     * @param string $icon <img>-tag for the icon
-     * @param string $onClick JavaScript onclick event for label/icon
-     * @param int $onlyCM ==1 and the element will NOT appear in clickmenus in the topframe (unless clickmenu is totally unavailable)! ==2 and the item will NEVER appear in top frame. (This is mostly for "less important" options since the top frame is not capable of holding so many elements horizontally)
-     * @param int $dontHide If set, the clickmenu layer will not hide itself onclick - used for secondary menus to appear...
-     * @return array $menuItem entry with 6 numerical entries: [0] is the HTML for display of the element with link and icon an mouseover etc., [1]-[5] is simply the input params passed through!
-     */
-    public function linkItem($str, $icon, $onClick, $onlyCM = 0, $dontHide = 0)
-    {
-        return [
-            '<a href="javascript:;" onclick="' . htmlspecialchars($onClick) . '">' . $str . $icon . '</a>',
-            $str,
-            $icon,
-            $onClick,
-            $onlyCM,
-            $dontHide
-        ];
-    }
-
-    /**
-     * Enabling / Disabling items based on list provided from GET var ($this->iParts[3])
-     *
-     * @param array $menuItems Menu items array
-     * @return array Menu items array, processed.
-     */
-    public function enableDisableItems($menuItems)
-    {
-        if ($this->iParts[3]) {
-            // Detect "only" mode: (only showing listed items)
-            if ($this->iParts[3][0] === '+') {
-                $this->iParts[3] = substr($this->iParts[3], 1);
-                $only = true;
-            } else {
-                $only = false;
-            }
-            // Do filtering:
-            // Transfer ONLY elements which are mentioned (or are spacers)
-            if ($only) {
-                $newMenuArray = [];
-                foreach ($menuItems as $key => $value) {
-                    if (GeneralUtility::inList($this->iParts[3], $key) || is_string($value) && $value === 'spacer') {
-                        $newMenuArray[$key] = $value;
-                    }
-                }
-                $menuItems = $newMenuArray;
-            } else {
-                // Traverse all elements except those listed (just unsetting them):
-                $elements = GeneralUtility::trimExplode(',', $this->iParts[3], true);
-                foreach ($elements as $value) {
-                    unset($menuItems[$value]);
-                }
-            }
-        }
-        // Return processed menu items:
-        return $menuItems;
-    }
-
-    /**
-     * Clean up spacers; Will remove any spacers in the start/end of menu items array plus any duplicates.
-     *
-     * @param array $menuItems Menu items array
-     * @return array Menu items array, processed.
-     */
-    public function cleanUpSpacers($menuItems)
-    {
-        // Remove doubles:
-        $prevItemWasSpacer = false;
-        foreach ($menuItems as $key => $value) {
-            if (is_string($value) && $value === 'spacer') {
-                if ($prevItemWasSpacer) {
-                    unset($menuItems[$key]);
-                }
-                $prevItemWasSpacer = true;
-            } else {
-                $prevItemWasSpacer = false;
-            }
-        }
-        // Remove first:
-        reset($menuItems);
-        $key = key($menuItems);
-        $value = current($menuItems);
-        if (is_string($value) && $value === 'spacer') {
-            unset($menuItems[$key]);
-        }
-        // Remove last:
-        end($menuItems);
-        $key = key($menuItems);
-        $value = current($menuItems);
-        if (is_string($value) && $value === 'spacer') {
-            unset($menuItems[$key]);
-        }
-        // Return processed menu items:
-        return $menuItems;
-    }
-
-    /**
-     * Get label from locallang_core.xlf:cm.*
-     *
-     * @param string $label The "cm."-suffix to get.
-     * @return string
-     */
-    public function label($label)
-    {
-        return htmlspecialchars($this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.' . $label));
-    }
-
-    /**
-     * Returns TRUE if there should be writing to the div-layers (commands sent to clipboard MUST NOT write to div-layers)
-     *
-     * @return bool
-     */
-    public function isCMlayers()
-    {
-        return !$this->CB;
-    }
-
-    /**
-     * Appends ".location" to input string
-     *
-     * @param string $str Input string, probably a JavaScript document reference
-     * @return string
-     */
-    public function frameLocation($str)
-    {
-        return $str . '.location';
-    }
-}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ContextMenu.php b/typo3/sysext/backend/Classes/ContextMenu/ContextMenu.php
new file mode 100644 (file)
index 0000000..b72da3e
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\ContextMenu;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class for generating the click menu
+ * @internal
+ */
+class ContextMenu
+{
+    /**
+     * Click menu item providers shipped with EXT:backend
+     *
+     * @var array
+     */
+    protected $itemProviders = [
+        ItemProviders\PageProvider::class,
+        ItemProviders\RecordProvider::class
+    ];
+
+    /**
+     * @param string $table
+     * @param string $identifier
+     * @param string $context
+     * @return array
+     */
+    public function getItems(string $table, string $identifier, string $context=''): array
+    {
+        $items = [];
+        $itemsProviders = $this->getAvailableProviders($table, $identifier, $context);
+
+        /** @var $provider \TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface */
+        foreach ($itemsProviders as $provider) {
+            $items = $provider->addItems($items);
+        }
+        return $this->cleanItems($items);
+    }
+
+    /**
+     * @param string $table
+     * @param string $identifier
+     * @param string $context
+     * @return array of \TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface
+     */
+    protected function getAvailableProviders(string $table, string $identifier, string $context): array
+    {
+        $providers = $this->itemProviders;
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'])) {
+            $providers = array_merge($this->itemProviders, $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders']);
+        }
+
+        $availableProviders = [];
+        foreach ($providers as $providerClass) {
+            $provider = GeneralUtility::makeInstance($providerClass, $table, $identifier, $context);
+            if ($provider->canHandle()) {
+                $priority = $provider->getPriority();
+                $availableProviders[$priority] = $provider;
+            }
+        }
+        krsort($availableProviders);
+        return $availableProviders;
+    }
+
+    /**
+     * Clean up double dividers.
+     * Don't render menu when there are no item or submenu.
+     *
+     * @param array $items
+     * @return array
+     */
+    protected function cleanItems(array $items): array
+    {
+        $canRender = false;
+        $prevItemWasDivider = false;
+        foreach ($items as $key => $item) {
+            if ($item['type'] === 'item') {
+                $canRender = true;
+                $prevItemWasDivider = false;
+                continue;
+            }
+            if ($item['type'] === 'divider') {
+                if ($prevItemWasDivider === true) {
+                    unset($items[$key]);
+                } else {
+                    $prevItemWasDivider = true;
+                }
+                continue;
+            }
+            if ($item['type'] === 'submenu') {
+                $childItems = $this->cleanItems($item['childItems']);
+                if (empty($childItems)) {
+                    unset($items[$key]);
+                } else {
+                    $items[$key]['childItems'] = $childItems;
+                    $canRender = true;
+                    $prevItemWasDivider = false;
+                }
+                continue;
+            }
+        }
+        //Remove first and last divider
+        $fistItem = reset($items);
+        if ($fistItem['type'] === 'divider') {
+            $key = key($items);
+            unset($items[$key]);
+        }
+        $lastItem = end($items);
+        if ($lastItem['type'] === 'divider') {
+            $key = key($items);
+            unset($items[$key]);
+        }
+        //no menu when there are no item or submenu
+        if (!$canRender) {
+            $items = [];
+        }
+        return $items;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ContextMenuAction.php b/typo3/sysext/backend/Classes/ContextMenu/ContextMenuAction.php
deleted file mode 100644 (file)
index 4ddb31b..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\ContextMenu;
-
-/*
- * 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!
- */
-
-/**
- * Context Menu Action
- */
-class ContextMenuAction
-{
-    /**
-     * Label
-     *
-     * @var string
-     */
-    protected $label = '';
-
-    /**
-     * Identifier
-     *
-     * @var string
-     */
-    protected $id = '';
-
-    /**
-     * Icon
-     *
-     * @var string
-     */
-    protected $icon = '';
-
-    /**
-     * Callback Action
-     *
-     * @var string
-     */
-    protected $callbackAction = '';
-
-    /**
-     * Type
-     *
-     * @var string
-     */
-    protected $type = '';
-
-    /**
-     * Child Action Collection
-     *
-     * @var ContextMenuActionCollection
-     */
-    protected $childActions;
-
-    /**
-     * Custom Action Attributes
-     *
-     * @var array
-     */
-    protected $customAttributes = [];
-
-    /**
-     * Returns the label
-     *
-     * @return string
-     */
-    public function getLabel()
-    {
-        return $this->label;
-    }
-
-    /**
-     * Sets the label
-     *
-     * @param string $label
-     */
-    public function setLabel($label)
-    {
-        $this->label = $label;
-    }
-
-    /**
-     * Returns the identifier
-     *
-     * @return string
-     */
-    public function getId()
-    {
-        return $this->id;
-    }
-
-    /**
-     * Sets the identifier
-     *
-     * @param string $id
-     */
-    public function setId($id)
-    {
-        $this->id = $id;
-    }
-
-    /**
-     * Returns the icon
-     *
-     * @return string
-     */
-    public function getIcon()
-    {
-        return $this->icon;
-    }
-
-    /**
-     * Sets the icon
-     *
-     * @param string $icon
-     * @return void
-     */
-    public function setIcon($icon)
-    {
-        $this->icon = $icon;
-    }
-
-    /**
-     * Returns the callback action
-     *
-     * @return string
-     */
-    public function getCallbackAction()
-    {
-        return $this->callbackAction;
-    }
-
-    /**
-     * Sets the callback action
-     *
-     * @param string $callbackAction
-     */
-    public function setCallbackAction($callbackAction)
-    {
-        $this->callbackAction = $callbackAction;
-    }
-
-    /**
-     * Returns the type
-     *
-     * @return string
-     */
-    public function getType()
-    {
-        return $this->type;
-    }
-
-    /**
-     * Sets the type
-     *
-     * @param string $type
-     * @return void
-     */
-    public function setType($type)
-    {
-        $this->type = $type;
-    }
-
-    /**
-     * Returns the child actions
-     *
-     * @return ContextMenuActionCollection
-     */
-    public function getChildActions()
-    {
-        return $this->childActions;
-    }
-
-    /**
-     * Sets the child actions
-     *
-     * @param ContextMenuActionCollection $actions
-     * @return void
-     */
-    public function setChildActions(ContextMenuActionCollection $actions)
-    {
-        $this->childActions = $actions;
-    }
-
-    /**
-     * Returns TRUE if the action has child actions
-     *
-     * @return bool
-     */
-    public function hasChildActions()
-    {
-        return $this->childActions !== null;
-    }
-
-    /**
-     * Sets the custom attributes
-     *
-     * @param array $customAttributes
-     * @return void
-     */
-    public function setCustomAttributes(array $customAttributes)
-    {
-        $this->customAttributes = $customAttributes;
-    }
-
-    /**
-     * Returns the custom attributes
-     *
-     * @return array
-     */
-    public function getCustomAttributes()
-    {
-        return $this->customAttributes;
-    }
-
-    /**
-     * Returns the action as an array
-     *
-     * @return array
-     */
-    public function toArray()
-    {
-        $arrayRepresentation = [
-            'label' => $this->getLabel(),
-            'id' => $this->getId(),
-            'icon' => $this->getIcon(),
-            'callbackAction' => $this->getCallbackAction(),
-            'type' => $this->getType(),
-            'customAttributes' => $this->getCustomAttributes()
-        ];
-        $arrayRepresentation['childActions'] = '';
-        if ($this->hasChildActions()) {
-            $arrayRepresentation['childActions'] = $this->childActions->toArray();
-        }
-        return $arrayRepresentation;
-    }
-}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ContextMenuActionCollection.php b/typo3/sysext/backend/Classes/ContextMenu/ContextMenuActionCollection.php
deleted file mode 100644 (file)
index c9b2b53..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\ContextMenu;
-
-/*
- * 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!
- */
-
-/**
- * Context Menu Action Collection
- */
-class ContextMenuActionCollection extends \ArrayObject
-{
-    /**
-     * Returns the collection in an array representation for e.g. serialization
-     *
-     * @return array
-     */
-    public function toArray()
-    {
-        $iterator = $this->getIterator();
-        $arrayRepresentation = [];
-        while ($iterator->valid()) {
-            $arrayRepresentation[] = $iterator->current()->toArray();
-            $iterator->next();
-        }
-        return $arrayRepresentation;
-    }
-}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/AbstractProvider.php b/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/AbstractProvider.php
new file mode 100644 (file)
index 0000000..25434c9
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\ContextMenu\ItemProviders;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Clipboard\Clipboard;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+
+/**
+ * Abstract provider is a base class for context menu item providers
+ */
+class AbstractProvider implements ProviderInterface
+{
+
+    /**
+     * Language Service property. Used to access localized labels
+     *
+     * @var LanguageService
+     */
+    protected $languageService;
+
+    /**
+     * @var BackendUserAuthentication
+     */
+    protected $backendUser;
+
+    /**
+     * @var \TYPO3\CMS\Backend\Clipboard\Clipboard
+     */
+    protected $clipboard;
+
+    /**
+     * Array of items the class is providing
+     *
+     * @var array
+     */
+    protected $itemsConfiguration = [];
+
+    /**
+     * Click menu items disabled by TSConfig
+     *
+     * @var array
+     */
+    protected $disabledItems = [];
+
+    /**
+     * Current table name
+     *
+     * @var string
+     */
+    protected $table = '';
+
+    /**
+     * @var string clicked record identifier (usually uid or file combined identifier)
+     */
+    protected $identifier = '';
+
+    /**
+     * Context - from where the click menu was triggered (e.g. 'tree')
+     *
+     * @var string
+     */
+    protected $context = '';
+
+    /**
+     * Lightweight constructor, just to be able to call ->canHandle(). Rest of the initialization is done
+     * in the initialize() method
+     *
+     * @param string $table
+     * @param string $identifier
+     * @param string $context
+     */
+    public function __construct(string $table, string $identifier, string $context='')
+    {
+        $this->table = $table;
+        $this->identifier = $identifier;
+        $this->context = $context;
+        $this->languageService = $GLOBALS['LANG'];
+        $this->backendUser = $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * Provider initialization, heavy stuff
+     */
+    protected function initialize()
+    {
+        $this->initClipboard();
+        $this->initDisabledItems();
+    }
+
+    /**
+     * Returns the provider priority which is used for determining the order in which providers are adding items
+     * to the result array. Highest priority means provider is evaluated first.
+     *
+     * @return int
+     */
+    public function getPriority(): int
+    {
+        return 100;
+    }
+
+    /**
+     * Whether this provider can handle given request (usually a check based on table, uid and context)
+     *
+     * @return bool
+     */
+    public function canHandle(): bool
+    {
+        return false;
+    }
+
+    /**
+     * Initialize clipboard object - necessary for all copy/cut/paste operations
+     */
+    protected function initClipboard()
+    {
+        $clipboard = GeneralUtility::makeInstance(Clipboard::class);
+        $clipboard->initializeClipboard();
+        // This locks the clipboard to the Normal for this request.
+        $clipboard->lockToNormal();
+        $this->clipboard = $clipboard;
+    }
+
+    /**
+     * Fills $this->disabledItems with the values from TSConfig.
+     * Disabled items can be set separately for each context.
+     */
+    protected function initDisabledItems()
+    {
+        $TSkey = $this->table . ($this->context ?  '.' . $this->context : '');
+        $this->disabledItems = GeneralUtility::trimExplode(',', $this->backendUser->getTSConfigVal('options.contextMenu.table.' . $TSkey . '.disableItems'), true);
+    }
+
+    /**
+     * Adds new items to the given array or modifies existing items
+     *
+     * @param array $items
+     * @return array
+     */
+    public function addItems(array $items): array
+    {
+        $this->initialize();
+        $items += $this->prepareItems($this->itemsConfiguration);
+        return $items;
+    }
+
+    /**
+     * Converts item configuration (from $this->itemsConfiguration) into an array ready for returning by controller
+     *
+     * @param array $itemsConfiguration
+     * @return array
+     */
+    protected function prepareItems(array $itemsConfiguration): array
+    {
+        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $items = [];
+        foreach ($itemsConfiguration as $name => $configuration) {
+            $type = !empty($configuration['type']) ? $configuration['type'] : 'item';
+            if ($this->canRender($name, $type)) {
+                $items[$name] = [
+                    'type' => $type,
+                    'label' => !empty($configuration['label']) ? htmlspecialchars($this->languageService->sL($configuration['label'])) : '',
+                    'icon' => !empty($configuration['iconIdentifier']) ? $iconFactory->getIcon($configuration['iconIdentifier'], Icon::SIZE_SMALL)->render() : '',
+                    'additionalAttributes' => $this->getAdditionalAttributes($name),
+                    'callbackAction' => !empty($configuration['callbackAction']) ? $configuration['callbackAction'] : ''
+                ];
+                if ($type === 'submenu') {
+                    $items[$name]['childItems'] = $this->prepareItems($configuration['childItems']);
+                }
+            }
+        }
+        return $items;
+    }
+
+    /**
+     * Returns an array of additional attributes for given item. Additional attributes are used to pass item specific data
+     * to the JS. E.g. message for the delete confirmation dialog
+     *
+     * @param string $itemName
+     * @return array
+     */
+    protected function getAdditionalAttributes(string $itemName): array
+    {
+        return [];
+    }
+
+    /**
+     * Checks whether certain item can be rendered (e.g. check for disabled items or permissions)
+     *
+     * @param string $itemName
+     * @param string $type
+     * @return bool
+     */
+    protected function canRender(string $itemName, string $type): bool
+    {
+        return true;
+    }
+
+    /**
+     * Returns a clicked record identifier
+     *
+     * @return string
+     */
+    protected function getIdentifier(): string
+    {
+        return '';
+    }
+
+}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/PageProvider.php b/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/PageProvider.php
new file mode 100644 (file)
index 0000000..de5b724
--- /dev/null
@@ -0,0 +1,437 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\ContextMenu\ItemProviders;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
+
+/**
+ * Context menu item provider for pages table
+ */
+class PageProvider extends RecordProvider
+{
+    /**
+     * @var string
+     */
+    protected $table = 'pages';
+
+    /**
+     * @var array
+     */
+    protected $itemsConfiguration = [
+        'view' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view',
+            'iconIdentifier' => 'actions-document-view',
+            'callbackAction' => 'viewRecord'
+        ],
+        'edit' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit',
+            'iconIdentifier' => 'actions-page-open',
+            'callbackAction' => 'editRecord'
+        ],
+        'new' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new',
+            'iconIdentifier' => 'actions-document-new',
+            'callbackAction' => 'newRecord'
+        ],
+        'info' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info',
+            'iconIdentifier' => 'actions-document-info',
+            'callbackAction' => 'openInfoPopUp'
+        ],
+        'divider1' => [
+            'type' => 'divider'
+        ],
+        'copy' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
+            'iconIdentifier' => 'actions-edit-copy',
+            'callbackAction' => 'copy'
+        ],
+        'copyRelease' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
+            'iconIdentifier' => 'actions-edit-copy-release',
+            'callbackAction' => 'clipboardRelease'
+        ],
+        'cut' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
+            'iconIdentifier' => 'actions-edit-cut',
+            'callbackAction' => 'cut'
+        ],
+        'cutRelease' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
+            'iconIdentifier' => 'actions-edit-cut-release',
+            'callbackAction' => 'clipboardRelease'
+        ],
+        'pasteAfter' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteafter',
+            'iconIdentifier' => 'actions-document-paste-after',
+            'callbackAction' => 'pasteAfter'
+        ],
+        'pasteInto' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
+            'iconIdentifier' => 'actions-document-paste-into',
+            'callbackAction' => 'pasteInto'
+        ],
+        'divider2' => [
+            'type' => 'divider'
+        ],
+        'more' => [
+            'type' => 'submenu',
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.more',
+            'iconIdentifier' => '',
+            'callbackAction' => 'openSubmenu',
+            'childItems' => [
+                'newWizard' => [
+                    'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_newWizard',
+                    'iconIdentifier' => 'actions-page-new',
+                    'callbackAction' => 'newPageWizard',
+                ],
+                'openListModule' => [
+                    'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_db_list',
+                    'iconIdentifier' => 'actions-system-list-open',
+                    'callbackAction' => 'openListModule',
+                ],
+                'mountAsTreeRoot' => [
+                    'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.tempMountPoint',
+                    'iconIdentifier' => 'actions-pagetree-mountroot',
+                    'callbackAction' => 'mountAsTreeRoot',
+                ],
+            ],
+        ],
+        'divider3' => [
+            'type' => 'divider'
+        ],
+        'enable' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enable',
+            'iconIdentifier' => 'actions-edit-unhide',
+            'callbackAction' => 'enableRecord',
+        ],
+        'disable' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable',
+            'iconIdentifier' => 'actions-edit-hide',
+            'callbackAction' => 'disableRecord',
+        ],
+        'delete' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete',
+            'iconIdentifier' => 'actions-edit-delete',
+            'callbackAction' => 'deleteRecord',
+        ],
+        'history' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history',
+            'iconIdentifier' => 'actions-document-history-open',
+            'callbackAction' => 'openHistoryPopUp',
+        ],
+        'clearCache' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.clear_cache',
+            'iconIdentifier' => 'actions-system-cache-clear',
+            'callbackAction' => 'clearCache',
+        ],
+    ];
+
+    /**
+     * Checks if the provider can add items to the menu
+     *
+     * @return bool
+     */
+    public function canHandle(): bool
+    {
+        return $this->table === 'pages';
+    }
+
+    /**
+     * @return int
+     */
+    public function getPriority(): int
+    {
+        return 100;
+    }
+
+    /**
+     * @param string $itemName
+     * @param string $type
+     * @return bool
+     */
+    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':
+                $canRender = $this->canBeViewed();
+                break;
+            case 'edit':
+                $canRender = $this->canBeEdited();
+                break;
+            case 'new':
+            case 'newWizard':
+                $canRender = $this->canBeCreated();
+                break;
+            case 'info':
+                $canRender = $this->canShowInfo();
+                break;
+            case 'enable':
+                $canRender = $this->canBeEnabled();
+                break;
+            case 'disable':
+                $canRender = $this->canBeDisabled();
+                break;
+            case 'delete':
+                $canRender = $this->canBeDeleted();
+                break;
+            case 'history':
+                $canRender = $this->canShowHistory();
+                break;
+            case 'openListModule':
+                $canRender = $this->canOpenListModule();
+                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();
+                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()
+    {
+        $this->pagePermissions = $this->backendUser->calcPerms($this->record);
+    }
+
+    /**
+     * Checks if the user may create pages below the given page
+     *
+     * @return bool
+     */
+    protected function canBeCreated(): bool
+    {
+        return $this->hasPagePermission(Permission::PAGE_NEW);
+    }
+
+    /**
+     * Checks if the user has editing rights
+     *
+     * @return bool
+     */
+    protected function canBeEdited(): bool
+    {
+        if ($this->isRoot()) {
+            return false;
+        }
+        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) {
+            return false;
+        }
+        if ($this->backendUser->isAdmin()) {
+            return true;
+        }
+        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) {
+            return false;
+        }
+        return !$this->isRecordLocked() && $this->hasPagePermission(Permission::PAGE_EDIT);
+    }
+
+    /**
+     * Check if a page is locked
+     *
+     * @return bool
+     */
+    protected function isRecordLocked(): bool
+    {
+        return (bool)$this->record['editlock'];
+    }
+
+    /**
+     * Checks if the page is allowed to can be cut
+     *
+     * @return bool
+     */
+    protected function canBeCut(): bool
+    {
+        return !$this->isWebMount()
+            && $this->canBeEdited()
+            && !$this->isDeletePlaceholder();
+    }
+
+    /**
+     * Checks if the page is allowed to be copied
+     *
+     * @return bool
+     */
+    protected function canBeCopied(): bool
+    {
+        return !$this->isRoot()
+            && !$this->isWebMount()
+            && !$this->isRecordInClipboard('copy')
+            && $this->hasPagePermission(Permission::PAGE_SHOW)
+            && !$this->isDeletePlaceholder();
+    }
+
+    /**
+     * Checks if something can be pasted into the node
+     *
+     * @return bool
+     */
+    protected function canBePastedInto(): bool
+    {
+        $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
+
+        return $clipboardElementCount
+            && $this->canBeCreated()
+            && !$this->isDeletePlaceholder();
+    }
+
+    /**
+     * Checks if something can be pasted after the node
+     *
+     * @return bool
+     */
+    protected function canBePastedAfter(): bool
+    {
+        $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
+        return $clipboardElementCount
+            && $this->canBeCreated()
+            && !$this->isDeletePlaceholder();
+    }
+
+    /**
+     * Checks if the page is allowed to be removed
+     *
+     * @return bool
+     */
+    protected function canBeRemoved(): bool
+    {
+        return !$this->isDeletePlaceholder()
+            && !$this->isRecordLocked()
+            && $this->hasPagePermission(Permission::PAGE_DELETE);
+    }
+
+    /**
+     * Checks if the page is allowed to be viewed in frontend
+     *
+     * @return bool
+     */
+    protected function canBeViewed(): bool
+    {
+        return !$this->isRoot() && !$this->isDeleted();
+    }
+
+    /**
+     * Checks if the page is allowed to show info
+     *
+     * @return bool
+     */
+    protected function canShowInfo(): bool
+    {
+        return !$this->isRoot();
+    }
+
+    /**
+     * Checks if the user has clear cache rights
+     *
+     * @return bool
+     */
+    protected function canClearCache(): bool
+    {
+        return !$this->isRoot()
+            && ($this->backendUser->isAdmin() || $this->backendUser->getTSConfigVal('options.clearCache.pages'));
+    }
+
+    /**
+     * Determines whether this node is deleted.
+     *
+     * @return bool
+     */
+    protected function isDeleted(): bool
+    {
+        return !empty($this->record['deleted']) || $this->isDeletePlaceholder();
+    }
+
+    /**
+     * Returns true if current record is a root page
+     *
+     * @return bool
+     */
+    protected function isRoot()
+    {
+        return (int)$this->identifier === 0;
+    }
+
+    /**
+     * Returns true if current record is a web mount
+     *
+     * @return bool
+     */
+    protected function isWebMount()
+    {
+        return in_array($this->identifier, $this->backendUser->returnWebmounts());
+    }
+
+    /**
+     * @param string $itemName
+     * @return array
+     */
+    protected function getAdditionalAttributes(string $itemName): array
+    {
+        $attributes = [];
+        if ($itemName === 'view') {
+            $attributes += $this->getViewAdditionalAttributes();
+        }
+        if ($itemName === 'delete') {
+            $attributes += $this->getDeleteAdditionalAttributes();
+        }
+        if ($itemName === 'pasteInto') {
+            $attributes += $this->getPasteAdditionalAttributes('into');
+        }
+        if ($itemName === 'pasteAfter') {
+            $attributes += $this->getPasteAdditionalAttributes('after');
+        }
+        return $attributes;
+    }
+
+    /**
+     * @return int
+     */
+    protected function getPreviewPid(): int
+    {
+        return (int)$this->record['uid'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/ProviderInterface.php b/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/ProviderInterface.php
new file mode 100644 (file)
index 0000000..4770f3f
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\ContextMenu\ItemProviders;
+
+/*
+ * 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!
+ */
+
+/**
+ * Interface for context menu items providers
+ */
+interface ProviderInterface
+{
+    /**
+     * @param array $items
+     * @return array
+     */
+    public function addItems(array $items): array;
+
+    /**
+     * Returns the priority of the provider. Higher priority value means provider is executed first
+     *
+     * @return int
+     */
+    public function getPriority(): int;
+
+    /**
+     * Checks if the provider can add items to the menu
+     *
+     * @return bool
+     */
+    public function canHandle(): bool;
+}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/RecordProvider.php b/typo3/sysext/backend/Classes/ContextMenu/ItemProviders/RecordProvider.php
new file mode 100644 (file)
index 0000000..2ce8607
--- /dev/null
@@ -0,0 +1,648 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\ContextMenu\ItemProviders;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Versioning\VersionState;
+
+/**
+ * Class responsible for providing click menu items for db records which don't have custom provider (as e.g. pages)
+ *
+ */
+class RecordProvider extends AbstractProvider
+{
+    /**
+     * Database record
+     *
+     * @var array
+     */
+    protected $record = [];
+
+    /**
+     * Database record of the page $this->record is placed on
+     *
+     * @var array
+     */
+    protected $pageRecord = [];
+
+    /**
+     * Local cache for the result of BackendUserAuthentication::calcPerms()
+     *
+     * @var int
+     */
+    protected $pagePermissions = 0;
+
+    /**
+     * @var array
+     */
+    protected $itemsConfiguration = [
+        'view' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view',
+            'iconIdentifier' => 'actions-document-view',
+            'callbackAction' => 'viewRecord'
+        ],
+        'edit' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit',
+            'iconIdentifier' => 'actions-open',
+            'callbackAction' => 'editRecord'
+        ],
+        'new' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new',
+            'iconIdentifier' => 'actions-document-new',
+            'callbackAction' => 'newRecord'
+        ],
+        'info' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info',
+            'iconIdentifier' => 'actions-document-info',
+            'callbackAction' => 'openInfoPopUp'
+        ],
+        'divider1' => [
+            'type' => 'divider'
+        ],
+        'copy' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
+            'iconIdentifier' => 'actions-edit-copy',
+            'callbackAction' => 'copy'
+        ],
+        'copyRelease' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
+            'iconIdentifier' => 'actions-edit-copy-release',
+            'callbackAction' => 'clipboardRelease'
+        ],
+        'cut' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
+            'iconIdentifier' => 'actions-edit-cut',
+            'callbackAction' => 'cut'
+        ],
+        'cutRelease' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
+            'iconIdentifier' => 'actions-edit-cut-release',
+            'callbackAction' => 'clipboardRelease'
+        ],
+        'pasteAfter' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteafter',
+            'iconIdentifier' => 'actions-document-paste-after',
+            'callbackAction' => 'pasteAfter'
+        ],
+        'divider2' => [
+            'type' => 'divider'
+        ],
+        'more' => [
+            'type' => 'submenu',
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.more',
+            'iconIdentifier' => '',
+            'callbackAction' => 'openSubmenu',
+            'childItems' => [
+                'newWizard' => [
+                    'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_newWizard',
+                    'iconIdentifier' => 'actions-document-new',
+                    'callbackAction' => 'newContentWizard',
+                ],
+                'openListModule' => [
+                    'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_db_list',
+                    'iconIdentifier' => 'actions-system-list-open',
+                    'callbackAction' => 'openListModule',
+                ],
+            ],
+        ],
+        'divider3' => [
+            'type' => 'divider'
+        ],
+        'enable' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enable',
+            'iconIdentifier' => 'actions-edit-unhide',
+            'callbackAction' => 'enableRecord',
+        ],
+        'disable' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable',
+            'iconIdentifier' => 'actions-edit-hide',
+            'callbackAction' => 'disableRecord',
+        ],
+        'delete' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete',
+            'iconIdentifier' => 'actions-edit-delete',
+            'callbackAction' => 'deleteRecord',
+        ],
+        'history' => [
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history',
+            'iconIdentifier' => 'actions-document-history-open',
+            'callbackAction' => 'openHistoryPopUp',
+        ],
+    ];
+
+    /**
+     * Whether this provider should kick in
+     *
+     * @return bool
+     */
+    public function canHandle(): bool
+    {
+        if (in_array($this->table, ['sys_file', 'sys_filemounts', 'sys_file_storage', 'pages'], true)
+            || strpos($this->table, '-drag') !== false) {
+            return false;
+        }
+        return isset($GLOBALS['TCA'][$this->table]);
+    }
+
+    /**
+     * Initialize db record
+     */
+    protected function initialize()
+    {
+        parent::initialize();
+        $this->record = BackendUtility::getRecordWSOL($this->table, $this->identifier);
+    }
+
+    /**
+     * Priority is set to lower then default value, in order to skip this provider if there is less generic provider available.
+     *
+     * @return int
+     */
+    public function getPriority(): int
+    {
+        return 60;
+    }
+
+    /**
+     * This provider works as a fallback if there is no provider dedicated for certain table, thus it's only kicking in when $items are empty.
+     *
+     * @param array $items
+     * @return array
+     */
+    public function addItems(array $items): array
+    {
+        if (!empty($items)) {
+            return $items;
+        }
+        $this->initialize();
+        return $this->prepareItems($this->itemsConfiguration);
+    }
+
+    /**
+     * Whether a given item can be rendered (e.g. user has enough permissions)
+     *
+     * @param string $itemName
+     * @param string $type
+     * @return bool
+     */
+    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':
+                $canRender = $this->canBeViewed();
+                break;
+            case 'edit':
+            case 'new':
+                $canRender = $this->canBeEdited();
+                break;
+            case 'newWizard':
+                $canRender = $this->canOpenNewCEWizard();
+                break;
+            case 'info':
+                $canRender = $this->canShowInfo();
+                break;
+            case 'enable':
+                $canRender = $this->canBeEnabled();
+                break;
+            case 'disable':
+                $canRender = $this->canBeDisabled();
+                break;
+            case 'delete':
+                $canRender = $this->canBeDeleted();
+                break;
+            case 'history':
+                $canRender = $this->canShowHistory();
+                break;
+            case 'openListModule':
+                $canRender = $this->canOpenListModule();
+                break;
+            case 'copy':
+                $canRender = $this->canBeCopied();
+                break;
+            case 'copyRelease':
+                $canRender = $this->isRecordInClipboard('copy');
+                break;
+            case 'cut':
+                $canRender = $this->canBeCut();
+                break;
+            case 'cutRelease':
+                $canRender = $this->isRecordInClipboard('cut');
+                break;
+            case 'pasteAfter':
+                $canRender = $this->canBePastedAfter();
+                break;
+        }
+        return $canRender;
+    }
+
+    /**
+     * Saves calculated permissions for a page containing given record, to speed things up
+     */
+    protected function initPermissions()
+    {
+        $this->pageRecord = BackendUtility::getRecord('pages', $this->record['pid']);
+        $this->pagePermissions = $this->backendUser->calcPerms($this->pageRecord);
+    }
+
+    /**
+     * Returns true if a current user have access to given permission
+     *
+     * @see BackendUserAuthentication::doesUserHaveAccess()
+     * @param int $permission
+     * @return bool
+     */
+    protected function hasPagePermission(int $permission): bool
+    {
+        return $this->backendUser->isAdmin() || ($this->pagePermissions & $permission) == $permission;
+    }
+
+    /**
+     * Additional attributes for JS
+     *
+     * @param string $itemName
+     * @return array
+     */
+    protected function getAdditionalAttributes(string $itemName): array
+    {
+        $attributes = [];
+        if ($itemName === 'view') {
+            $attributes += $this->getViewAdditionalAttributes();
+        }
+        if ($itemName === 'newWizard' && $this->table === 'tt_content') {
+            $tsConfig = BackendUtility::getModTSconfig($this->record['pid'], 'mod');
+            $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
+                ? $tsConfig['properties']['newContentElementWizard.']['override']
+                : 'new_content_element';
+            $urlParameters = [
+                'id' => $this->record['pid'],
+                'sys_language_uid' => $this->record['sys_language_uid'],
+                'colPos' => $this->record['colPos'],
+                'uid_pid' => -$this->record['uid']
+            ];
+            $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
+            $attributes += [
+                'data-new-wizard-url' => htmlspecialchars($url)
+            ];
+        }
+        if ($itemName === 'delete') {
+            $attributes += $this->getDeleteAdditionalAttributes();
+        }
+        if ($itemName === 'openListModule') {
+            $attributes += [
+                'data-page-uid' => $this->record['pid']
+            ];
+        }
+        if ($itemName === 'pasteAfter') {
+            $attributes += $this->getPasteAdditionalAttributes('after');
+        }
+        return $attributes;
+    }
+
+    /**
+     * Additional attributes for the 'view' item
+     *
+     * @return array
+     */
+    protected function getViewAdditionalAttributes(): array
+    {
+        $attributes = [];
+        $viewLink = $this->getViewLink();
+        if ($viewLink) {
+            $attributes += [
+                'data-preview-url' => htmlspecialchars($viewLink),
+            ];
+        }
+        return $attributes;
+    }
+
+    /**
+     * Additional attributes for the pasteInto and pasteAfter items
+     *
+     * @param string $type "after" or "into"
+     * @return array
+     */
+    protected function getPasteAdditionalAttributes(string $type): array
+    {
+        $attributes = [];
+        if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
+            $selItem = $this->clipboard->getSelectedRecord();
+            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
+
+            $confirmMessage = sprintf(
+                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.'
+                    . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_' . $type),
+                GeneralUtility::fixed_lgd_cs($selItem['_RECORD_TITLE'], $this->backendUser->uc['titleLen']),
+                GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($this->table, $this->record), $this->backendUser->uc['titleLen'])
+            );
+            $attributes += [
+                'data-title' => htmlspecialchars($title),
+                'data-message' => htmlspecialchars($confirmMessage)
+            ];
+        }
+        return $attributes;
+    }
+
+    /**
+     * Additional data for a "delete" action (confirmation modal title and message)
+     *
+     * @return array
+     */
+    protected function getDeleteAdditionalAttributes(): array
+    {
+        $attributes = [];
+        if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
+            $recordTitle = GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($this->table, $this->record), $this->backendUser->uc['titleLen']);
+
+            $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
+            $confirmMessage = sprintf(
+                $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'),
+                $recordTitle
+            );
+            $confirmMessage .= BackendUtility::referenceCount(
+                $this->table,
+                $this->record['uid'],
+                ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord')
+            );
+            $confirmMessage .= BackendUtility::translationCount(
+                $this->table,
+                $this->record['uid'],
+                ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
+            );
+            $attributes += [
+                'data-title' => htmlspecialchars($title),
+                'data-message' => htmlspecialchars($confirmMessage)
+            ];
+        }
+        return $attributes;
+    }
+
+    /**
+     * Returns id of the Page used for preview
+     *
+     * @return int
+     */
+    protected function getPreviewPid(): int
+    {
+        return (int)$this->record['pid'];
+    }
+
+    /**
+     * Returns the view link
+     *
+     * @return string
+     */
+    protected function getViewLink(): string
+    {
+        $javascriptLink = BackendUtility::viewOnClick($this->getPreviewPid());
+        $extractedLink = '';
+        if (preg_match('/window\\.open\\(\'([^\']+)\'/i', $javascriptLink, $match)) {
+            // Clean JSON-serialized ampersands ('&')
+            // @see GeneralUtility::quoteJSvalue()
+            $extractedLink = json_decode('"' . trim($match[1], '"') . '"');
+        }
+        return $extractedLink;
+    }
+
+    /**
+     * Checks if the page is allowed to show info
+     *
+     * @return bool
+     */
+    protected function canShowInfo(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Checks if the page is allowed to show info
+     *
+     * @return bool
+     */
+    protected function canShowHistory(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Checks if the record can be previewed in frontend
+     *
+     * @return bool
+     */
+    protected function canBeViewed(): bool
+    {
+        return $this->table === 'tt_content';
+    }
+
+    /**
+     * Whether a record can be edited
+     *
+     * @return bool
+     */
+    protected function canBeEdited(): bool
+    {
+        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) {
+            return false;
+        }
+        if ($this->backendUser->isAdmin()) {
+            return true;
+        }
+        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) {
+            return false;
+        }
+
+        $access = !$this->isRecordLocked()
+            && $this->backendUser->check('tables_modify', $this->table)
+            && $this->hasPagePermission(Permission::CONTENT_EDIT);
+        return $access;
+    }
+
+    /**
+     * Checks if the user has the right to delete the page
+     *
+     * @return bool
+     */
+    protected function canBeDeleted(): bool
+    {
+        $disableDeleteTS = $this->backendUser->getTSConfig('options.disableDelete');
+        $disableDelete = (bool) trim(isset($disableDeleteTS['properties'][$this->table]) ? $disableDeleteTS['properties'][$this->table] : (string)$disableDeleteTS['value']);
+        return !$disableDelete && $this->canBeEdited();
+    }
+
+    /**
+     * Returns true if current record can be unhidden/enabled
+     *
+     * @return bool
+     */
+    protected function canBeEnabled(): bool
+    {
+        return $this->hasDisableColumnWithValue(1) && $this->canBeEdited();
+    }
+
+    /**
+     * Returns true if current record can be hidden
+     *
+     * @return bool
+     */
+    protected function canBeDisabled(): bool
+    {
+        return $this->hasDisableColumnWithValue(0) && $this->canBeEdited();
+    }
+
+    /**
+     * Returns true new content element wizard can be shown
+     *
+     * @return bool
+     */
+    protected function canOpenNewCEWizard(): bool
+    {
+        $tsConfig = BackendUtility::getModTSconfig($this->record['pid'], 'mod.web_layout');
+        $wizardEnabled = true;
+        if (isset($tsConfig['properties']['disableNewContentElementWizard'])) {
+            $wizardEnabled = false;
+        }
+        return $this->table === 'tt_content' && $wizardEnabled && $this->canBeEdited();
+    }
+
+    /**
+     * @return bool
+     */
+    protected function canOpenListModule(): bool
+    {
+        return $this->backendUser->check('modules', 'web_list');
+    }
+
+    /**
+     * @return bool
+     */
+    protected function canBeCopied(): bool
+    {
+        return !$this->isRecordInClipboard('copy')
+            && !$this->isRecordATranslation();
+    }
+
+    /**
+     * @return bool
+     */
+    protected function canBeCut(): bool
+    {
+        return !$this->isRecordInClipboard('cut')
+            && $this->canBeEdited()
+            && !$this->isRecordATranslation();
+    }
+
+    /**
+     * Paste after is only shown for records from the same table (comparing record in clipboard and record clicked)
+     *
+     * @return bool
+     */
+    protected function canBePastedAfter(): bool
+    {
+        $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
+
+        return $clipboardElementCount
+            && $this->backendUser->check('tables_modify', $this->table)
+            && $this->hasPagePermission(Permission::CONTENT_EDIT);
+    }
+
+    /**
+     * Checks if table have "disable" column (e.g. "hidden"), if user has access to this column
+     * and if it contains given value
+     *
+     * @param int $value
+     * @return bool
+     */
+    protected function hasDisableColumnWithValue(int $value): bool
+    {
+        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['enablecolumns']['disabled'])) {
+            $hiddenFieldName = $GLOBALS['TCA'][$this->table]['ctrl']['enablecolumns']['disabled'];
+            if (
+                $hiddenFieldName !== '' && !empty($GLOBALS['TCA'][$this->table]['columns'][$hiddenFieldName]['exclude'])
+                && $this->backendUser->check('non_exclude_fields', $this->table . ':' . $hiddenFieldName)
+            ) {
+                return (int)$this->record[$hiddenFieldName] === (int)$value;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Record is locked if page is locked or page is not locked but record is
+     *
+     * @return bool
+     */
+    protected function isRecordLocked(): bool
+    {
+        return (int)$this->pageRecord['editlock'] === 1
+            || isset($GLOBALS['TCA'][$this->table]['ctrl']['editlock'])
+            && (int)$this->record[$GLOBALS['TCA'][$this->table]['ctrl']['editlock']] === 1;
+    }
+
+    /**
+     * Returns true is a current record is a delete placeholder
+     *
+     * @return bool
+     */
+    protected function isDeletePlaceholder(): bool
+    {
+        if (!isset($this->record['t3ver_state'])) {
+            return false;
+        }
+        return VersionState::cast($this->record['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER);
+    }
+
+    /**
+     * Checks if current record is in the "normal" pad of the clipboard
+     *
+     * @param string $mode "copy", "cut" or '' for any mode
+     * @return bool
+     */
+    protected function isRecordInClipboard(string $mode = ''): bool
+    {
+        $isSelected = '';
+        if ($this->clipboard->current === 'normal') {
+            $isSelected = $this->clipboard->isSelected($this->table, $this->record['uid']);
+        }
+        return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
+    }
+
+    /**
+     * Returns true is a record ia a translation
+     *
+     * @return bool
+     */
+    protected function isRecordATranslation(): bool
+    {
+        return BackendUtility::isTableLocalizable($this->table) && (int)$this->record[$GLOBALS['TCA'][$this->table]['ctrl']['transOrigPointerField']] !== 0;
+    }
+
+    /**
+     * @return string
+     */
+    protected function getIdentifier(): string
+    {
+        return $this->record['uid'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/Pagetree/ContextMenuDataProvider.php b/typo3/sysext/backend/Classes/ContextMenu/Pagetree/ContextMenuDataProvider.php
deleted file mode 100644 (file)
index 497b45b..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\ContextMenu\Pagetree;
-
-/*
- * 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!
- */
-
-use TYPO3\CMS\Backend\ContextMenu\ContextMenuAction;
-use TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection;
-use TYPO3\CMS\Backend\Tree\TreeNode;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Imaging\Icon;
-use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Context Menu Data Provider for the Page Tree
- */
-class ContextMenuDataProvider
-{
-    /**
-     * List of actions that are generally disabled
-     *
-     * @var array
-     */
-    protected $disableItems = [];
-
-    /**
-     * Context Menu Type (e.g. table.pages, table.tt_content)
-     *
-     * @var string
-     */
-    protected $contextMenuType = '';
-
-    /**
-     * Old Context Menu Options (access mapping)
-     *
-     * Note: Only option with different namings are mapped!
-     *
-     * @var array
-     */
-    protected $legacyContextMenuMapping = [
-        'hide' => 'disable',
-        'paste' => 'pasteInto,pasteAfter',
-        'mount_as_treeroot' => 'mountAsTreeroot'
-    ];
-
-    /**
-     * Returns the context menu type
-     *
-     * @return string
-     */
-    public function getContextMenuType()
-    {
-        return $this->contextMenuType;
-    }
-
-    /**
-     * Sets the context menu type
-     *
-     * @param string $contextMenuType
-     * @return void
-     */
-    public function setContextMenuType($contextMenuType)
-    {
-        $this->contextMenuType = $contextMenuType;
-    }
-
-    /**
-     * Returns the configuration of the specified context menu type
-     *
-     * @return array
-     */
-    protected function getConfiguration()
-    {
-        $contextMenuActions = $this->getBackendUser()
-            ->getTSConfig('options.contextMenu.' . $this->contextMenuType . '.items');
-        return $contextMenuActions['properties'];
-    }
-
-    /**
-     * Evaluates a given display condition and returns TRUE if the condition matches
-     *
-     * Examples:
-     * getContextInfo|inCutMode:1 || isInCopyMode:1
-     * isLeafNode:1
-     * isLeafNode:1 && isInCutMode:1
-     *
-     * @param TreeNode $node
-     * @param string $displayCondition
-     * @return bool
-     */
-    protected function evaluateDisplayCondition(TreeNode $node, $displayCondition)
-    {
-        if ($displayCondition === '') {
-            return true;
-        }
-        // Parse condition string
-        $conditions = [];
-        preg_match_all('/(.+?)(>=|<=|!=|=|>|<)(.+?)(\\|\\||&&|$)/is', $displayCondition, $conditions);
-        $lastResult = false;
-        $chainType = '';
-        $amountOfConditions = count($conditions[0]);
-        for ($i = 0; $i < $amountOfConditions; ++$i) {
-            // Check method for existence
-            $method = trim($conditions[1][$i]);
-            list($method, $index) = explode('|', $method);
-            if (!method_exists($node, $method)) {
-                continue;
-            }
-            // Fetch compare value
-            $returnValue = call_user_func([$node, $method]);
-            if (is_array($returnValue)) {
-                $returnValue = $returnValue[$index];
-            }
-            // Compare fetched and expected values
-            $operator = trim($conditions[2][$i]);
-            $expected = trim($conditions[3][$i]);
-            if ($operator === '=') {
-                $returnValue = $returnValue == $expected;
-            } elseif ($operator === '>') {
-                $returnValue = $returnValue > $expected;
-            } elseif ($operator === '<') {
-                $returnValue = $returnValue < $expected;
-            } elseif ($operator === '>=') {
-                $returnValue = $returnValue >= $expected;
-            } elseif ($operator === '<=') {
-                $returnValue = $returnValue <= $expected;
-            } elseif ($operator === '!=') {
-                $returnValue = $returnValue != $expected;
-            } else {
-                $returnValue = false;
-                $lastResult = false;
-            }
-            // Chain last result and the current if requested
-            if ($chainType === '||') {
-                $lastResult = $lastResult || $returnValue;
-            } elseif ($chainType === '&&') {
-                $lastResult = $lastResult && $returnValue;
-            } else {
-                $lastResult = $returnValue;
-            }
-            // Save chain type for the next condition
-            $chainType = trim($conditions[4][$i]);
-        }
-        return $lastResult;
-    }
-
-    /**
-     * Returns the next context menu level
-     *
-     * @param array $actions
-     * @param TreeNode $node
-     * @param int $level
-     * @return ContextMenuActionCollection
-     */
-    protected function getNextContextMenuLevel(array $actions, TreeNode $node, $level = 0)
-    {
-        /** @var $actionCollection ContextMenuActionCollection */
-        $actionCollection = GeneralUtility::makeInstance(ContextMenuActionCollection::class);
-        /** @var $iconFactory IconFactory */
-        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-        if ($level > 5) {
-            return $actionCollection;
-        }
-        $type = '';
-        foreach ($actions as $index => $actionConfiguration) {
-            if (substr($index, -1) !== '.') {
-                $type = $actionConfiguration;
-                if ($type !== 'DIVIDER') {
-                    continue;
-                }
-            }
-            if (!in_array($type, ['DIVIDER', 'SUBMENU', 'ITEM'], true)) {
-                continue;
-            }
-            /** @var $action ContextMenuAction */
-            $action = GeneralUtility::makeInstance(ContextMenuAction::class);
-            $action->setId($index);
-            if ($type === 'DIVIDER') {
-                $action->setType('divider');
-            } else {
-                if (in_array($actionConfiguration['name'], $this->disableItems, true)
-                    || isset($actionConfiguration['displayCondition'])
-                    && trim($actionConfiguration['displayCondition']) !== ''
-                    && !$this->evaluateDisplayCondition($node, $actionConfiguration['displayCondition'])
-                ) {
-                    unset($action);
-                    continue;
-                }
-                $label = htmlspecialchars($this->getLanguageService()->sL($actionConfiguration['label']));
-                if ($type === 'SUBMENU') {
-                    $action->setType('submenu');
-                    $action->setChildActions($this->getNextContextMenuLevel($actionConfiguration, $node, $level + 1));
-                } else {
-                    $action->setType('action');
-                    $action->setCallbackAction($actionConfiguration['callbackAction']);
-                    if (is_array($actionConfiguration['customAttributes.'])) {
-                        if (!empty($actionConfiguration['customAttributes.']['contentUrl'])) {
-                            $actionConfiguration['customAttributes.']['contentUrl'] = $this
-                                ->replaceModuleTokenInContentUrl(
-                                    $actionConfiguration['customAttributes.']['contentUrl']
-                                );
-                        }
-                        $action->setCustomAttributes($actionConfiguration['customAttributes.']);
-                    }
-                }
-                $action->setLabel($label);
-                if (!isset($actionConfiguration['iconName'])) {
-                    $actionConfiguration['iconName'] = 'miscellaneous-placeholder';
-                }
-                $action->setIcon($iconFactory->getIcon($actionConfiguration['iconName'], Icon::SIZE_SMALL)->render());
-            }
-            $actionCollection->offsetSet($level . (int)$index, $action);
-        }
-        $actionCollection->ksort();
-        return $actionCollection;
-    }
-
-    /**
-     * Add the CSRF token to the module URL if a "M" parameter is found
-     *
-     * @param string $contentUrl
-     * @return string
-     */
-    protected function replaceModuleTokenInContentUrl($contentUrl)
-    {
-        $parsedUrl = parse_url($contentUrl);
-        parse_str($parsedUrl['query'], $urlParameters);
-        if (!empty($urlParameters['M'])) {
-            $moduleName = $urlParameters['M'];
-            unset($urlParameters['M']);
-            $contentUrl = BackendUtility::getModuleUrl($moduleName, $urlParameters);
-        }
-        return $contentUrl;
-    }
-
-    /**
-     * Fetches the items that should be disabled from the context menu
-     *
-     * @return array
-     */
-    protected function getDisableActions()
-    {
-        $tsConfig = $this->getBackendUser()
-            ->getTSConfig('options.contextMenu.' . $this->getContextMenuType() . '.disableItems');
-        $disableItems = [];
-        if (trim($tsConfig['value']) !== '') {
-            $disableItems = GeneralUtility::trimExplode(',', $tsConfig['value']);
-        }
-        $tsConfig = $this->getBackendUser()->getTSConfig('options.contextMenu.pageTree.disableItems');
-        $oldDisableItems = [];
-        if (trim($tsConfig['value']) !== '') {
-            $oldDisableItems = GeneralUtility::trimExplode(',', $tsConfig['value']);
-        }
-        $additionalItems = [];
-        foreach ($oldDisableItems as $item) {
-            if (empty($this->legacyContextMenuMapping[$item])) {
-                $additionalItems[] = $item;
-                continue;
-            }
-            if (strpos($this->legacyContextMenuMapping[$item], ',')) {
-                $actions = GeneralUtility::trimExplode(',', $this->legacyContextMenuMapping[$item]);
-                $additionalItems = array_merge($additionalItems, $actions);
-            } else {
-                $additionalItems[] = $item;
-            }
-        }
-        $disableItems = array_merge($disableItems, $additionalItems);
-
-        // Further manipulation of disableItems array via hook
-        // @internal: This is an internal hook for extension impexp only, this hook may change without further notice
-        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'])
-            && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'])
-        ) {
-            $hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'];
-            foreach ($hooks as $hook) {
-                $parameterArray = [
-                    'disableItems' => &$disableItems,
-                ];
-                $null = null;
-                GeneralUtility::callUserFunction($hook, $parameterArray, $null);
-            }
-        }
-
-        return $disableItems;
-    }
-
-    /**
-     * Returns the actions for the node
-     * @param TreeNode $node
-     *
-     * @return array|ContextMenuActionCollection
-     */
-    public function getActionsForNode(TreeNode $node)
-    {
-        $this->disableItems = $this->getDisableActions();
-        $configuration = $this->getConfiguration();
-        $contextMenuActions = [];
-        if (is_array($configuration)) {
-            $contextMenuActions = $this->getNextContextMenuLevel($configuration, $node);
-        }
-        return $contextMenuActions;
-    }
-
-    /**
-     * Returns LanguageService
-     *
-     * @return \TYPO3\CMS\Lang\LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-
-    /**
-     * Returns the current BE user.
-     *
-     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
-     */
-    protected function getBackendUser()
-    {
-        return $GLOBALS['BE_USER'];
-    }
-}
diff --git a/typo3/sysext/backend/Classes/ContextMenu/Pagetree/Extdirect/ContextMenuConfiguration.php b/typo3/sysext/backend/Classes/ContextMenu/Pagetree/Extdirect/ContextMenuConfiguration.php
deleted file mode 100644 (file)
index fb6964d..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\ContextMenu\Pagetree\Extdirect;
-
-/*
- * 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!
- */
-use TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection;
-use TYPO3\CMS\Backend\ContextMenu\Pagetree\ContextMenuDataProvider;
-use TYPO3\CMS\Backend\Tree\Pagetree\Commands;
-use TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode;
-use TYPO3\CMS\Backend\Tree\TreeNode;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Context Menu of the Page Tree
- */
-class ContextMenuConfiguration
-{
-    /**
-     * Data Provider
-     *
-     * @var ContextMenuDataProvider
-     */
-    protected $dataProvider = null;
-
-    /**
-     * @param ContextMenuDataProvider $dataProvider
-     * @return void
-     */
-    public function setDataProvider(ContextMenuDataProvider $dataProvider)
-    {
-        $this->dataProvider = $dataProvider;
-    }
-
-    /**
-     * @return ContextMenuDataProvider
-     */
-    public function getDataProvider()
-    {
-        return $this->dataProvider;
-    }
-    /**
-     * Sets the data provider
-     *
-     * @return void
-     */
-    protected function initDataProvider()
-    {
-        $dataProvider = GeneralUtility::makeInstance(ContextMenuDataProvider::class);
-        $this->setDataProvider($dataProvider);
-    }
-
-    /**
-     * Returns the actions for the given node information's
-     *
-     * @param \stdClass $nodeData
-     * @return array
-     */
-    public function getActionsForNodeArray($nodeData)
-    {
-        $node = GeneralUtility::makeInstance(PagetreeNode::class, (array)$nodeData);
-        $node->setRecord(Commands::getNodeRecord($node->getId()));
-        $this->initDataProvider();
-        $this->dataProvider->setContextMenuType('table.' . $node->getType());
-        $actionCollection = $this->dataProvider->getActionsForNode($node);
-        $actions = [];
-        if ($actionCollection instanceof ContextMenuActionCollection) {
-            $actions = $actionCollection->toArray();
-        }
-        return $actions;
-    }
-
-    /**
-     * Unused for this implementation
-     *
-     * @see getActionsForNodeArray()
-     * @param TreeNode $node
-     * @return array
-     */
-    public function getActionsForNode(TreeNode $node)
-    {
-    }
-}
index ba7be90..a8570b0 100644 (file)
@@ -148,6 +148,9 @@ class BackendController
         // load Modals
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
 
         // load Modals
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
 
+        // load ContextMenu
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
+
         // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage', 'function(Storage) {
                        Storage.Persistent.load(' . json_encode($this->getBackendUser()->uc) . ');
         // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage', 'function(Storage) {
                        Storage.Persistent.load(' . json_encode($this->getBackendUser()->uc) . ');
@@ -407,6 +410,8 @@ class BackendController
             $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
             $this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', BackendUtility::getModuleUrl('db_new'));
             $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
             $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
             $this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', BackendUtility::getModuleUrl('db_new'));
             $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
+            $this->pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', BackendUtility::getModuleUrl('tce_db'));
+            $this->pageRenderer->addInlineSetting('WebLayout', 'moduleUrl', BackendUtility::getModuleUrl('web_layout'));
         }
     }
 
         }
     }
 
diff --git a/typo3/sysext/backend/Classes/Controller/ClickMenuController.php b/typo3/sysext/backend/Classes/Controller/ClickMenuController.php
deleted file mode 100644 (file)
index dc689a1..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\Controller;
-
-/*
- * 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!
- */
-
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Backend\ClickMenu\ClickMenu;
-use TYPO3\CMS\Backend\Clipboard\Clipboard;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Script Class for the Context Sensitive Menu in TYPO3 (rendered in top frame, normally writing content dynamically to list frames).
- * @see \TYPO3\CMS\Backend\Template\DocumentTemplate::getContextMenuCode()
- */
-class ClickMenuController
-{
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
-        $GLOBALS['SOBE'] = $this;
-        // Setting pseudo module name
-        $this->MCONF['name'] = 'xMOD_alt_clickmenu.php';
-    }
-
-    /**
-     * this is an intermediate clickmenu handler
-     *
-     * @param ServerRequestInterface $request
-     * @param ResponseInterface $response
-     * @return ResponseInterface
-     */
-    public function getContextMenuAction(ServerRequestInterface $request, ResponseInterface $response)
-    {
-        /** @var Clipboard $clipObj */
-        $clipObj = GeneralUtility::makeInstance(Clipboard::class);
-        $clipObj->initializeClipboard();
-        // This locks the clipboard to the Normal for this request.
-        $clipObj->lockToNormal();
-        // Update clipboard if some actions are sent.
-        $clipObj->setCmd($request->getQueryParams()['CB']);
-        $clipObj->cleanCurrent();
-        // Saves
-        $clipObj->endClipboard();
-        // Create clickmenu object
-        $clickMenu = GeneralUtility::makeInstance(ClickMenu::class);
-        // Set internal vars in clickmenu object:
-        $clickMenu->clipObj = $clipObj;
-        // Setting internal array of classes for extending the clickmenu:
-        $clickMenu->extClassArray = $GLOBALS['TBE_MODULES_EXT']['xMOD_alt_clickmenu']['extendCMclasses'];
-
-        $content = $clickMenu->init();
-        if (!is_array($content)) {
-            $content = [];
-        }
-        $response->getBody()->write(json_encode($content));
-        return $response;
-    }
-
-    /**
-     * Returns LanguageService
-     *
-     * @return \TYPO3\CMS\Lang\LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-}
index f2b17b6..2899feb 100644 (file)
@@ -165,7 +165,7 @@ class NewContentElementController extends AbstractModule
         // We keep this here in case somebody relies on it in a hook or alike
         $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
         // Setting up the context sensitive menu:
         // We keep this here in case somebody relies on it in a hook or alike
         $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
         // Setting up the context sensitive menu:
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         // Getting the current page and receiving access information (used in main())
         $perms_clause = $this->getBackendUser()->getPagePermsClause(1);
         $this->pageInfo = BackendUtility::readPageAccess($this->id, $perms_clause);
         // Getting the current page and receiving access information (used in main())
         $perms_clause = $this->getBackendUser()->getPagePermsClause(1);
         $this->pageInfo = BackendUtility::readPageAccess($this->id, $perms_clause);
diff --git a/typo3/sysext/backend/Classes/Controller/ContextMenuController.php b/typo3/sysext/backend/Classes/Controller/ContextMenuController.php
new file mode 100644 (file)
index 0000000..a9fa905
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\ContextMenu\ContextMenu;
+use TYPO3\CMS\Backend\Clipboard\Clipboard;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+
+/**
+ * Script Class for the Context Sensitive Menu in TYPO3
+ */
+class ContextMenuController
+{
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
+    }
+
+    /**
+     * Renders a context menu
+     *
+     * @param ServerRequestInterface $request
+     * @param ResponseInterface $response
+     * @return ResponseInterface
+     */
+    public function getContextMenuAction(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
+    {
+        $contextMenu = GeneralUtility::makeInstance(ContextMenu::class);
+
+        $params = $request->getQueryParams();
+        $context = isset($params['context']) ? $params['context'] : '';
+        $items = $contextMenu->getItems($params['table'], $params['uid'], $context);
+        if (!is_array($items)) {
+            $items = [];
+        }
+        $response->getBody()->write(json_encode($items));
+        return $response;
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @param ResponseInterface $response
+     * @return ResponseInterface
+     */
+    public function clipboardAction(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
+    {
+        /** @var Clipboard $clipboard */
+        $clipboard = GeneralUtility::makeInstance(Clipboard::class);
+        $clipboard->initializeClipboard();
+        $clipboard->lockToNormal();
+
+        $clipboard->setCmd($request->getQueryParams()['CB']);
+        $clipboard->cleanCurrent();
+
+        $clipboard->endClipboard();
+        $response->getBody()->write(json_encode([]));
+        return $response;
+    }
+
+    /**
+     * Returns LanguageService
+     *
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
index a7b280d..02ee7c9 100644 (file)
@@ -796,7 +796,7 @@ class EditDocumentController extends AbstractModule
             $javascript . $previewCode
         );
         // Setting up the context sensitive menu:
             $javascript . $previewCode
         );
         // Setting up the context sensitive menu:
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
 
         $this->emitFunctionAfterSignal(__FUNCTION__);
     }
 
         $this->emitFunctionAfterSignal(__FUNCTION__);
     }
index 17d7682..694657e 100644 (file)
@@ -125,7 +125,7 @@ class CreateFolderController extends AbstractModule
             'combined_identifier' => $this->folderObject->getCombinedIdentifier(),
         ];
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
             'combined_identifier' => $this->folderObject->getCombinedIdentifier(),
         ];
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $this->moduleTemplate->addJavaScriptCode(
             'CreateFolderInlineJavaScript',
             'var path = "' . $this->target . '";
         $this->moduleTemplate->addJavaScriptCode(
             'CreateFolderInlineJavaScript',
             'var path = "' . $this->target . '";
index e257284..31c45f2 100644 (file)
@@ -109,7 +109,7 @@ class FileUploadController extends AbstractModule
         }
 
         // Setting up the context sensitive menu
         }
 
         // Setting up the context sensitive menu
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
 
         // building pathInfo for metaInformation
         $pathInfo = [
 
         // building pathInfo for metaInformation
         $pathInfo = [
index ead4607..d0122d6 100644 (file)
@@ -118,7 +118,7 @@ class RenameFileController extends AbstractModule
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
 
         // Setting up the context sensitive menu
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
 
         // Setting up the context sensitive menu
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
 
         // Add javaScript
         $this->moduleTemplate->addJavaScriptCode(
 
         // Add javaScript
         $this->moduleTemplate->addJavaScriptCode(
index 2087879..73e45c3 100644 (file)
@@ -136,7 +136,7 @@ class ReplaceFileController extends AbstractModule
             'combined_identifier' => $this->fileOrFolderObject->getCombinedIdentifier(),
         ];
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
             'combined_identifier' => $this->fileOrFolderObject->getCombinedIdentifier(),
         ];
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $this->moduleTemplate->addJavaScriptCode(
             'ReplaceFileOnlineJavaScript',
             'function backToList() {top.goToModule("file_FilelistList");}'
         $this->moduleTemplate->addJavaScriptCode(
             'ReplaceFileOnlineJavaScript',
             'function backToList() {top.goToModule("file_FilelistList");}'
index 2e8c852..d117353 100644 (file)
@@ -152,7 +152,7 @@ class FileSystemNavigationFrameController
 
         // Adding javascript for drag & drop activation and highlighting
         $pageRenderer = $this->moduleTemplate->getPageRenderer();
 
         // Adding javascript for drag & drop activation and highlighting
         $pageRenderer = $this->moduleTemplate->getPageRenderer();
-        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree', 'function() {
             DragDrop.table = "folders";
                        Tree.registerDragDropHandlers();
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree', 'function() {
             DragDrop.table = "folders";
                        Tree.registerDragDropHandlers();
index b5c5490..e460e7b 100644 (file)
@@ -184,7 +184,7 @@ class NewRecordController extends AbstractModule
         $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
         $this->pagesOnly = GeneralUtility::_GP('pagesOnly');
         // Setting up the context sensitive menu:
         $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
         $this->pagesOnly = GeneralUtility::_GP('pagesOnly');
         // Setting up the context sensitive menu:
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
         // Creating content
         $this->content = '';
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
         // Creating content
         $this->content = '';
index 8de4e4f..d2f0c41 100644 (file)
@@ -825,7 +825,7 @@ class PageLayoutController
     public function renderContent()
     {
         $this->moduleTemplate->getPageRenderer()->loadJquery();
     public function renderContent()
     {
         $this->moduleTemplate->getPageRenderer()->loadJquery();
-        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         /** @var $dbList \TYPO3\CMS\Backend\View\PageLayoutView */
         $dbList = GeneralUtility::makeInstance(PageLayoutView::class);
         $dbList->thumbs = $this->imagemode;
         /** @var $dbList \TYPO3\CMS\Backend\View\PageLayoutView */
         $dbList = GeneralUtility::makeInstance(PageLayoutView::class);
         $dbList->thumbs = $this->imagemode;
index ca5a066..c80b755 100644 (file)
@@ -85,7 +85,7 @@ class OuterWrapContainer extends AbstractContainer
                 $pageTitle = sprintf($label, $tableTitle, $pageTitle);
             }
         } else {
                 $pageTitle = sprintf($label, $tableTitle, $pageTitle);
             }
         } else {
-            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid'], 1, '', '+copy,info,edit,view');
+            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
             $newOrUid = ' <span class="typo3-TCEforms-recUid">[' . htmlspecialchars($row['uid']) . ']</span>';
 
             // @todo: getRecordTitlePrep applies an htmlspecialchars here
             $newOrUid = ' <span class="typo3-TCEforms-recUid">[' . htmlspecialchars($row['uid']) . ']</span>';
 
             // @todo: getRecordTitlePrep applies an htmlspecialchars here
index 58f48c0..8452515 100644 (file)
@@ -90,7 +90,7 @@ class MetaInformation
     }
 
     /**
     }
 
     /**
-     * Setting page icon with clickMenu + uid for docheader
+     * Setting page icon with context menu + uid for docheader
      *
      * @return string Record info
      */
      *
      * @return string Record info
      */
@@ -105,7 +105,7 @@ class MetaInformation
         $uid = '';
         $title = '';
         $additionalInfo = (!empty($pageRecord['_additional_info']) ? $pageRecord['_additional_info'] : '');
         $uid = '';
         $title = '';
         $additionalInfo = (!empty($pageRecord['_additional_info']) ? $pageRecord['_additional_info'] : '');
-        // Add icon with clickMenu, etc:
+        // Add icon with context menu, etc:
         // If there IS a real page
         if (is_array($pageRecord) && $pageRecord['uid']) {
             $toolTip = BackendUtility::getRecordToolTip($pageRecord, 'pages');
         // If there IS a real page
         if (is_array($pageRecord) && $pageRecord['uid']) {
             $toolTip = BackendUtility::getRecordToolTip($pageRecord, 'pages');
@@ -134,7 +134,7 @@ class MetaInformation
                         Icon::SIZE_SMALL
                     )->render() . '</span>';
                 }
                         Icon::SIZE_SMALL
                     )->render() . '</span>';
                 }
-                $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, $pageRecord['combined_identifier']);
+                $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, 'sys_file', $pageRecord['combined_identifier']);
             } catch (ResourceDoesNotExistException $e) {
                 $theIcon = '';
             }
             } catch (ResourceDoesNotExistException $e) {
                 $theIcon = '';
             }
@@ -153,7 +153,7 @@ class MetaInformation
             $uid = '0';
             $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
         }
             $uid = '0';
             $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
         }
-        // Setting icon with clickMenu + uid
+        // Setting icon with context menu + uid
         return $theIcon .
             ' <strong>' . htmlspecialchars($title) . ($uid !== '' ? '&nbsp;[' . $uid . ']' : '') . '</strong>' .
             (!empty($additionalInfo) ? ' ' . htmlspecialchars($additionalInfo) : '');
         return $theIcon .
             ' <strong>' . htmlspecialchars($title) . ($uid !== '' ? '&nbsp;[' . $uid . ']' : '') . '</strong>' .
             (!empty($additionalInfo) ? ' ' . htmlspecialchars($additionalInfo) : '');
index 1468ce2..d8e5416 100644 (file)
@@ -1125,7 +1125,7 @@ function jumpToUrl(URL) {
     {
         GeneralUtility::logDeprecatedFunction();
         $this->pageRenderer->loadJquery();
     {
         GeneralUtility::logDeprecatedFunction();
         $this->pageRenderer->loadJquery();
-        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
     }
 
     /**
     }
 
     /**
@@ -1453,14 +1453,14 @@ function jumpToUrl(URL) {
     }
 
     /**
     }
 
     /**
-     * Setting page icon with clickmenu + uid for docheader
+     * Setting page icon with context menu + uid for docheader
      *
      * @param array $pageRecord Current page
      * @return string Page info
      */
     protected function getPageInfo($pageRecord)
     {
      *
      * @param array $pageRecord Current page
      * @return string Page info
      */
     protected function getPageInfo($pageRecord)
     {
-        // Add icon with clickmenu, etc:
+        // Add icon with context menu, etc:
         // If there IS a real page
         if (is_array($pageRecord) && $pageRecord['uid']) {
             $alttext = BackendUtility::getRecordIconAltText($pageRecord, 'pages');
         // If there IS a real page
         if (is_array($pageRecord) && $pageRecord['uid']) {
             $alttext = BackendUtility::getRecordIconAltText($pageRecord, 'pages');
@@ -1481,7 +1481,7 @@ function jumpToUrl(URL) {
             $uid = '0';
             $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
         }
             $uid = '0';
             $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
         }
-        // Setting icon with clickmenu + uid
+        // Setting icon with context menu + uid
         $pageInfo = $theIcon . '<strong>' . htmlspecialchars($title) . '&nbsp;[' . $uid . ']</strong>';
         return $pageInfo;
     }
         $pageInfo = $theIcon . '<strong>' . htmlspecialchars($title) . '&nbsp;[' . $uid . ']</strong>';
         return $pageInfo;
     }
index bc99db5..8087ea1 100644 (file)
@@ -179,7 +179,9 @@ class FolderTreeView extends AbstractTreeView
         if (!$this->ext_IconMode) {
             // Check storage access to wrap with click menu
             if (!$folderObject instanceof InaccessibleFolder) {
         if (!$this->ext_IconMode) {
             // Check storage access to wrap with click menu
             if (!$folderObject instanceof InaccessibleFolder) {
-                $theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $folderObject->getCombinedIdentifier(), '', 0);
+                $tableName = $this->getTableNameForClickMenu($folderObject);
+                $theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $tableName, $folderObject->getCombinedIdentifier(), 'tree');
+
             }
         } elseif ($this->ext_IconMode === 'titlelink') {
             $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
             }
         } elseif ($this->ext_IconMode === 'titlelink') {
             $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
@@ -205,7 +207,8 @@ class FolderTreeView extends AbstractTreeView
             return $title;
         }
         $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
             return $title;
         }
         $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
-        $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $folderObject->getCombinedIdentifier(), '', 0, ('&bank=' . $this->bank), '', true);
+        $tableName = $this->getTableNameForClickMenu($folderObject);
+        $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $tableName, $folderObject->getCombinedIdentifier(), 'tree', '', '', true);
 
         return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
     }
 
         return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
     }
@@ -555,6 +558,24 @@ class FolderTreeView extends AbstractTreeView
     }
 
     /**
     }
 
     /**
+     * Returns table name for click menu
+     *
+     * @param Folder $folderObject
+     * @return string
+     */
+    protected function getTableNameForClickMenu(Folder $folderObject)
+    {
+        if (strpos($folderObject->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
+            $tableName = 'sys_filemounts';
+        } elseif ($folderObject->getIdentifier() === $folderObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
+            $tableName = 'sys_file_storage';
+        } else {
+            $tableName = 'sys_file';
+        }
+        return $tableName;
+    }
+
+    /**
      * Counts the number of directories in a file path.
      *
      * @param Folder $folderObject File path.
      * Counts the number of directories in a file path.
      *
      * @param Folder $folderObject File path.
index 1b8dd36..cd57fbf 100644 (file)
@@ -3037,21 +3037,17 @@ class BackendUtility
      * Returns $str wrapped in a link which will activate the context sensitive
      * menu for the record ($table/$uid) or file ($table = file)
      * The link will load the top frame with the parameter "&item" which is the table, uid
      * Returns $str wrapped in a link which will activate the context sensitive
      * menu for the record ($table/$uid) or file ($table = file)
      * The link will load the top frame with the parameter "&item" which is the table, uid
-     * and listFrame arguments imploded by "|": rawurlencode($table.'|'.$uid.'|'.$listFr)
+     * and context arguments imploded by "|": rawurlencode($table.'|'.$uid.'|'.$context)
      *
      * @param string $content String to be wrapped in link, typ. image tag.
      * @param string $table Table name/File path. If the icon is for a database
      * record, enter the tablename from $GLOBALS['TCA']. If a file then enter
      * the absolute filepath
      *
      * @param string $content String to be wrapped in link, typ. image tag.
      * @param string $table Table name/File path. If the icon is for a database
      * record, enter the tablename from $GLOBALS['TCA']. If a file then enter
      * the absolute filepath
-     * @param int $uid If icon is for database record this is the UID for the
-     * record from $table
-     * @param bool $listFrame Tells the top frame script that the link is coming
-     * from a "list" frame which means a frame from within the backend content frame.
-     * @param string $addParams Additional GET parameters for the link to the
-     * ClickMenu AJAX request
-     * @param string $enDisItems Enable / Disable click menu items.
-     * Example: "+new,view" will display ONLY these two items (and any spacers
-     * in between), "new,view" will display all BUT these two items.
+     * @param int|string $uid If icon is for database record this is the UID for the
+     * record from $table or identifier for sys_file record
+     * @param string $context Set tree if menu is called from tree view
+     * @param string $_addParams NOT IN USE
+     * @param string $_enDisItems NOT IN USE
      * @param bool $returnTagParameters If set, will return only the onclick
      * JavaScript, not the whole link.
      *
      * @param bool $returnTagParameters If set, will return only the onclick
      * JavaScript, not the whole link.
      *
@@ -3061,18 +3057,16 @@ class BackendUtility
         $content,
         $table,
         $uid = 0,
         $content,
         $table,
         $uid = 0,
-        $listFrame = true,
-        $addParams = '',
-        $enDisItems = '',
+        $context = '',
+        $_addParams = '',
+        $_enDisItems = '',
         $returnTagParameters = false
     ) {
         $tagParameters = [
         $returnTagParameters = false
     ) {
         $tagParameters = [
-            'class' => 't3-js-clickmenutrigger',
+            'class' => 't3js-contextmenutrigger',
             'data-table' => $table,
             'data-table' => $table,
-            'data-uid' => (int)$uid !== 0 ? (int)$uid : '',
-            'data-listframe' => $listFrame,
-            'data-iteminfo' => str_replace('+', '%2B', $enDisItems),
-            'data-parameters' => $addParams,
+            'data-uid' => $uid,
+            'data-context' => $context
         ];
 
         if ($returnTagParameters) {
         ];
 
         if ($returnTagParameters) {
index b62eaf2..09951be 100644 (file)
@@ -1620,10 +1620,9 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
         $allowDragAndDrop = $this->isDragAndDropAllowed($row);
         $additionalIcons = [];
         if ($row['sys_language_uid'] > 0 && $this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid'])) {
         $allowDragAndDrop = $this->isDragAndDropAllowed($row);
         $additionalIcons = [];
         if ($row['sys_language_uid'] > 0 && $this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid'])) {
-            $disabledClickMenuItems = 'new,move';
             $allowDragAndDrop = false;
         }
             $allowDragAndDrop = false;
         }
-        $additionalIcons[] = $this->getIcon('tt_content', $row, $disabledClickMenuItems) . ' ';
+        $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
         $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
         // Get record locking status:
         if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
         $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
         // Get record locking status:
         if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
@@ -1791,10 +1790,7 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
                                 $icon = BackendUtility::wrapClickMenuOnIcon(
                                     $icon,
                                     $tableName,
                                 $icon = BackendUtility::wrapClickMenuOnIcon(
                                     $icon,
                                     $tableName,
-                                    $shortcutRecord['uid'],
-                                    1,
-                                    '',
-                                    '+copy,info,edit,view'
+                                    $shortcutRecord['uid']
                                 );
                                 $shortcutContent[] = $icon
                                     . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
                                 );
                                 $shortcutContent[] = $icon
                                     . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
@@ -2218,8 +2214,8 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
      * @return void
      *
      * @see \TYPO3\CMS\Recordlist\RecordList::main()
      * @return void
      *
      * @see \TYPO3\CMS\Recordlist\RecordList::main()
-     * @see \TYPO3\CMS\Backend\Controller\ClickMenuController::main()
-     * @see \TYPO3\CMS\Filelist\Controller\FileListController::main()
+     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
+     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
      */
     protected function initializeClipboard()
     {
      */
     protected function initializeClipboard()
     {
@@ -2334,7 +2330,7 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
         $this->counter++;
         // The icon with link
         if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
         $this->counter++;
         // The icon with link
         if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
-            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid'], true, '', $enabledClickMenuItems);
+            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
         }
         return $icon;
     }
         }
         return $icon;
     }
index 3ec080a..e39ca1b 100644 (file)
@@ -67,7 +67,7 @@ class PageTreeView extends BrowseTreeView
         }
         // Wrap icon in click-menu link.
         if (!$this->ext_IconMode) {
         }
         // Wrap icon in click-menu link.
         if (!$this->ext_IconMode) {
-            $thePageIcon = BackendUtility::wrapClickMenuOnIcon($thePageIcon, 'pages', $row['uid'], 0, '&bank=' . $this->bank);
+            $thePageIcon = BackendUtility::wrapClickMenuOnIcon($thePageIcon, 'pages', $row['uid'], 'tree');
         } elseif ($this->ext_IconMode === 'titlelink') {
             $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->treeName) . ');';
             $thePageIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $thePageIcon . '</a>';
         } elseif ($this->ext_IconMode === 'titlelink') {
             $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->treeName) . ');';
             $thePageIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $thePageIcon . '</a>';
@@ -110,7 +110,7 @@ class PageTreeView extends BrowseTreeView
             unset($_params);
         }
         $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $bank . ');';
             unset($_params);
         }
         $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $bank . ');';
-        $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', 'pages', $row['uid'], 0, ('&bank=' . $this->bank), '', true);
+        $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', 'pages', $row['uid'], 'tree', '', '', true);
 
         $thePageTitle = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '"' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
         // Wrap title in a drag/drop span.
 
         $thePageTitle = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '"' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
         // Wrap title in a drag/drop span.
index b8a8747..2e7a611 100644 (file)
@@ -165,7 +165,13 @@ return [
     // Load context menu for
     'contextmenu' => [
         'path' => '/context-menu',
     // Load context menu for
     'contextmenu' => [
         'path' => '/context-menu',
-        'target' => Controller\ClickMenuController::class . '::getContextMenuAction'
+        'target' => Controller\ContextMenuController::class . '::getContextMenuAction'
+    ],
+
+    // Load context menu for
+    'contextmenu_clipboard' => [
+        'path' => '/context-menu/clipboard',
+        'target' => Controller\ContextMenuController::class . '::clipboardAction'
     ],
 
     // Process data handler commands
     ],
 
     // Process data handler commands
index 782f3f0..7aee3a2 100644 (file)
@@ -9909,6 +9909,7 @@ div#contentMenu1 {
   min-width: 150px;
 }
 .context-menu .list-group-item {
   min-width: 150px;
 }
 .context-menu .list-group-item {
+  cursor: pointer;
   padding: 5px;
   border-bottom-color: transparent;
   border-top-color: transparent;
   padding: 5px;
   border-bottom-color: transparent;
   border-top-color: transparent;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ClickMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/ClickMenu.js
deleted file mode 100644 (file)
index 150c020..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * 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!
- */
-
-/**
- * Module: TYPO3/CMS/Backend/ClickMenu
- * Javascript container used to load the clickmenu via AJAX
- * to render the result in a layer next to the mouse cursor
- */
-define(['jquery'], function($) {
-
-       /**
-        *
-        * @type {{mousePos: {X: null, Y: null}, delayClickMenuHide: boolean}}
-        * @exports TYPO3/CMS/Backend/ClickMenu
-        */
-       var ClickMenu = {
-               mousePos: {
-                       X: null,
-                       Y: null
-               },
-               delayClickMenuHide: false
-       };
-
-       /**
-        * Initialize events
-        */
-       ClickMenu.initializeEvents = function() {
-               $(document).on('click contextmenu', '.t3-js-clickmenutrigger', function(event) {
-                       // if there is an other "inline" onclick setting, clickmenu is not triggered
-                       // usually this is the case for the foldertree
-                       if ($(this).prop('onclick') && event.type === 'click') {
-                               return;
-                       }
-                       event.preventDefault();
-                       ClickMenu.show(
-                               $(this).data('table'),
-                               $(this).data('uid'),
-                               $(this).data('listframe'),
-                               $(this).data('iteminfo'),
-                               $(this).data('parameters')
-                       );
-               });
-
-               // register mouse movement inside the document
-               $(document).on('mousemove', ClickMenu.storeMousePositionEvent);
-       };
-
-       /**
-        * Main function, called from most clickmenu links
-        *
-        * @param {String} table Table from where info should be fetched
-        * @param {(String|Number)} uid The UID of the item
-        * @param {String} listFr list Frame?
-        * @param {String} enDisItems Items to disable / enable
-        * @param {String} addParams Additional params
-        * @return void
-        */
-       ClickMenu.show = function(table, uid, listFr, enDisItems, addParams) {
-               var parameters = '';
-
-               if (typeof table !== 'undefined') {
-                       parameters += 'table=' + encodeURIComponent(table);
-               }
-               if (typeof uid !== 'undefined') {
-                       parameters += (parameters.length > 0 ? '&' : '') + 'uid=' + uid;
-               }
-               if (typeof listFr !== 'undefined') {
-                       parameters += (parameters.length > 0 ? '&' : '') + 'listFr=' + listFr;
-               }
-               if (typeof enDisItems !== 'undefined') {
-                       parameters += (parameters.length > 0 ? '&' : '') + 'enDisItems=' + enDisItems;
-               }
-               if (typeof addParams !== 'undefined') {
-                       parameters += (parameters.length > 0 ? '&' : '') + 'addParams=' + addParams;
-               }
-               this.fetch(parameters);
-       };
-
-       /**
-        * Make the AJAX request
-        *
-        * @param {array} parameters Parameters sent to the server
-        * @return void
-        */
-       ClickMenu.fetch = function(parameters) {
-               var url = TYPO3.settings.ajaxUrls['contextmenu'];
-               if (parameters) {
-                       url += ((url.indexOf('?') == -1) ? '?' : '&') + parameters;
-               }
-               $.ajax(url).done(function(response) {
-                       // No response content, usually because a copy/paste action was triggered and just the frame needs
-                       // to be reloaded.
-                       if (typeof response.items === "undefined") {
-                               var res = parameters.match(/&reloadListFrame=(0|1|2)(&|$)/);
-                               if (res !== null && parseInt(res[1], 0)) {
-                                       top.list_frame.location.reload(true);
-                               }
-                       } else {
-                               ClickMenu.populateData(response.items, response.level);
-                       }
-               });
-       };
-
-       /**
-        * fills the clickmenu with content and displays it correctly
-        * depending on the mouse position
-        *
-        * @param {array} items The data that will be put in the menu
-        * @param {Number} level The depth of the clickmenu
-        */
-       ClickMenu.populateData = function(items, level) {
-               this.initializeClickMenuContainer();
-
-               level = parseInt(level, 10) || 0;
-               var $obj = $('#contentMenu' + level);
-
-               if ($obj.length && (level === 0 || $('#contentMenu' + (level-1)).is(':visible'))) {
-                       $obj.html('<div class="list-group">' + items.join('') + '</div>');
-                       var x = this.mousePos.X;
-                       var y = this.mousePos.Y;
-                       var dimsWindow = {
-                               width: $(document).width()-20, // saving margin for scrollbars
-                               height: $(document).height()
-                       };
-
-                       // dimensions for the clickmenu
-                       var dims = {
-                               width: $obj.width(),
-                               height: $obj.height()
-                       };
-
-                       var relative = {
-                               X: this.mousePos.X - $(document).scrollLeft(),
-                               Y: this.mousePos.Y - $(document).scrollTop()
-                       };
-
-                       // adjusting the Y position of the layer to fit it into the window frame
-                       // if there is enough space above then put it upwards,
-                       // otherwise adjust it to the bottom of the window
-                       if (dimsWindow.height - dims.height < relative.Y) {
-                               if (relative.Y > dims.height) {
-                                       y -= (dims.height - 10);
-                               } else {
-                                       y += (dimsWindow.height - dims.height - relative.Y);
-                               }
-                       }
-                       // adjusting the X position like Y above, but align it to the left side of the viewport if it does not fit completely
-                       if (dimsWindow.width - dims.width < relative.X) {
-                               if (relative.X > dims.width) {
-                                       x -= (dims.width - 10);
-                               } else if ((dimsWindow.width - dims.width - relative.X) < $(document).scrollLeft()) {
-                                       x = $(document).scrollLeft();
-                               } else {
-                                       x += (dimsWindow.width - dims.width - relative.X);
-                               }
-                       }
-
-                       $obj.css({left: x + 'px', top: y + 'px'}).show();
-               }
-       };
-
-       /**
-        * event handler function that saves the
-        * actual position of the mouse
-        * in the Clickmenu object
-        *
-        * @param {Event} event The event object
-        */
-       ClickMenu.storeMousePositionEvent = function(event) {
-               ClickMenu.mousePos.X = event.pageX;
-               ClickMenu.mousePos.Y = event.pageY;
-               ClickMenu.mouseOutFromMenu('#contentMenu0');
-               ClickMenu.mouseOutFromMenu('#contentMenu1');
-       };
-
-       /**
-        * hides a visible menu if the mouse has moved outside
-        * of the object
-        *
-        * @param {Object} obj The object to hide
-        */
-       ClickMenu.mouseOutFromMenu = function(obj) {
-               var $element = $(obj);
-
-               if ($element.length > 0 && $element.is(':visible') && !this.within($element, this.mousePos.X, this.mousePos.Y)) {
-                       this.hide($element);
-               } else if ($element.length > 0 && $element.is(':visible')) {
-                       this.delayClickMenuHide = true;
-               }
-       };
-
-       /**
-        *
-        * @param {Object} $element
-        * @param {Number} x
-        * @param {Number} y
-        * @returns {Boolean}
-        */
-       ClickMenu.within = function($element, x, y) {
-               var offset = $element.offset();
-           return (y >= offset.top &&
-                               y <  offset.top + $element.height() &&
-                               x >= offset.left &&
-                               x <  offset.left + $element.width());
-       };
-
-       /**
-        * hides a clickmenu
-        *
-        * @param {Object} obj The clickmenu object to hide
-        */
-       ClickMenu.hide = function(obj) {
-               this.delayClickMenuHide = false;
-               window.setTimeout(function() {
-                       if (!ClickMenu.delayClickMenuHide) {
-                               $(obj).hide();
-                       }
-               }, 500);
-       };
-
-       /**
-        * hides all clickmenus
-        */
-       ClickMenu.hideAll = function() {
-               this.hide('#contentMenu0');
-               this.hide('#contentMenu1');
-       };
-
-       /**
-        * manipulates the DOM to add the divs needed for clickmenu at the bottom of the <body>-tag
-        */
-       ClickMenu.initializeClickMenuContainer = function() {
-               if ($('#contentMenu0').length === 0) {
-                       var code = '<div id="contentMenu0" class="context-menu"></div><div id="contentMenu1" class="context-menu" style="display: block;"></div>';
-                       $('body').append(code);
-               }
-       };
-
-       ClickMenu.initializeEvents();
-
-       // make it globally available
-       TYPO3.ClickMenu = ClickMenu;
-       return ClickMenu;
-});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenu.js
new file mode 100644 (file)
index 0000000..057c275
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * 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!
+ */
+
+/**
+ * Module: TYPO3/CMS/Backend/ContextMenu
+ * Javascript container used to load the context menu via AJAX
+ * to render the result in a layer next to the mouse cursor
+ */
+define(['jquery', 'TYPO3/CMS/Backend/ContextMenuActions'], function ($, ContextMenuActions) {
+
+    /**
+     *
+     * @type {{mousePos: {X: null, Y: null}, delayContextMenuHide: boolean}}
+     * @exports TYPO3/CMS/Backend/ContextMenu
+     */
+    var ContextMenu = {
+        mousePos: {
+            X: null,
+            Y: null
+        },
+        delayContextMenuHide: false,
+        record: {
+            uid: null,
+            table: null
+        }
+    };
+
+    /**
+     * Initialize events
+     */
+    ContextMenu.initializeEvents = function () {
+        $(document).on('click contextmenu', '.t3js-contextmenutrigger', function (event) {
+            // if there is an other "inline" onclick setting, context menu is not triggered
+            // usually this is the case for the foldertree
+            if ($(this).prop('onclick') && event.type === 'click') {
+                return;
+            }
+            event.preventDefault();
+            ContextMenu.show(
+                $(this).data('table'),
+                $(this).data('uid'),
+                $(this).data('context'),
+                $(this).data('iteminfo'),
+                $(this).data('parameters')
+            );
+        });
+
+        // register mouse movement inside the document
+        $(document).on('mousemove', ContextMenu.storeMousePositionEvent);
+    };
+
+    /**
+     * Main function, called from most context menu links
+     *
+     * @param {String} table Table from where info should be fetched
+     * @param {(String|Number)} uid The UID of the item
+     * @param {String} context Context of the item
+     * @param {String} enDisItems Items to disable / enable
+     * @param {String} addParams Additional params
+     * @return void
+     */
+    ContextMenu.show = function (table, uid, context, enDisItems, addParams) {
+        ContextMenu.record = null;
+        ContextMenu.record = {table: table, uid: uid};
+
+        var parameters = '';
+
+        if (typeof table !== 'undefined') {
+            parameters += 'table=' + encodeURIComponent(table);
+        }
+        if (typeof uid !== 'undefined') {
+            parameters += (parameters.length > 0 ? '&' : '') + 'uid=' + uid;
+        }
+        if (typeof context !== 'undefined') {
+            parameters += (parameters.length > 0 ? '&' : '') + 'context=' + context;
+        }
+        if (typeof enDisItems !== 'undefined') {
+            parameters += (parameters.length > 0 ? '&' : '') + 'enDisItems=' + enDisItems;
+        }
+        if (typeof addParams !== 'undefined') {
+            parameters += (parameters.length > 0 ? '&' : '') + 'addParams=' + addParams;
+        }
+        this.fetch(parameters);
+    };
+
+    /**
+     * Make the AJAX request
+     *
+     * @param {array} parameters Parameters sent to the server
+     * @return void
+     */
+    ContextMenu.fetch = function (parameters) {
+        var url = TYPO3.settings.ajaxUrls['contextmenu'];
+        if (parameters) {
+            url += ((url.indexOf('?') == -1) ? '?' : '&') + parameters;
+        }
+        $.ajax(url).done(function (response) {
+            if (typeof response !== "undefined" && Object.keys(response).length > 0) {
+                ContextMenu.populateData(response, 0);
+            }
+        });
+    };
+
+    /**
+     * fills the context menu with content and displays it correctly
+     * depending on the mouse position
+     *
+     * @param {array} items The data that will be put in the menu
+     * @param {Number} level The depth of the context menu
+     */
+    ContextMenu.populateData = function (items, level) {
+        this.initializeContextMenuContainer();
+
+        level = parseInt(level, 10) || 0;
+        var $obj = $('#contentMenu' + level);
+
+        if ($obj.length && (level === 0 || $('#contentMenu' + (level - 1)).is(':visible'))) {
+            var elements = ContextMenu.drawMenu(items, level);
+            $obj.html('<div class="list-group">' + elements + '</div>');
+
+            $('a.list-group-item', $obj).click(function (event) {
+                event.preventDefault();
+
+                if ($(this).hasClass('list-group-item-submenu')) {
+                    ContextMenu.openSubmenu(level, $(this));
+                    return;
+                }
+
+                var callbackName = $(this).data('callback-action');
+                var callbackModule = $(this).data('callback-module');
+                var clickItem = $(this);
+                if (callbackModule) {
+                    require([callbackModule], function (callbackModule) {
+                        callbackModule[callbackName].bind(clickItem)(ContextMenu.record.table, ContextMenu.record.uid);
+                    });
+                } else if (ContextMenuActions && ContextMenuActions[callbackName]) {
+                    ContextMenuActions[callbackName].bind(clickItem)(ContextMenu.record.table, ContextMenu.record.uid);
+                } else {
+                    console.log('action: ' + callbackName + ' not found');
+                }
+                ContextMenu.hideAll();
+            });
+
+            $obj.css(ContextMenu.getPosition($obj)).show();
+        }
+    };
+
+    ContextMenu.openSubmenu = function (level, $item) {
+        var $obj = $('#contentMenu' + (level + 1)).html('');
+        $item.next().find('.list-group').clone(true).appendTo($obj);
+        $obj.css(ContextMenu.getPosition($obj)).show();
+    };
+
+    ContextMenu.getPosition = function ($obj) {
+        var x = this.mousePos.X;
+        var y = this.mousePos.Y;
+        var dimsWindow = {
+            width: $(document).width() - 20, // saving margin for scrollbars
+            height: $(document).height()
+        };
+
+        // dimensions for the context menu
+        var dims = {
+            width: $obj.width(),
+            height: $obj.height()
+        };
+
+        var relative = {
+            X: this.mousePos.X - $(document).scrollLeft(),
+            Y: this.mousePos.Y - $(document).scrollTop()
+        };
+
+        // adjusting the Y position of the layer to fit it into the window frame
+        // if there is enough space above then put it upwards,
+        // otherwise adjust it to the bottom of the window
+        if (dimsWindow.height - dims.height < relative.Y) {
+            if (relative.Y > dims.height) {
+                y -= (dims.height - 10);
+            } else {
+                y += (dimsWindow.height - dims.height - relative.Y);
+            }
+        }
+        // adjusting the X position like Y above, but align it to the left side of the viewport if it does not fit completely
+        if (dimsWindow.width - dims.width < relative.X) {
+            if (relative.X > dims.width) {
+                x -= (dims.width - 10);
+            } else if ((dimsWindow.width - dims.width - relative.X) < $(document).scrollLeft()) {
+                x = $(document).scrollLeft();
+            } else {
+                x += (dimsWindow.width - dims.width - relative.X);
+            }
+        }
+        return {left: x + 'px', top: y + 'px'};
+    };
+
+    /**
+     * fills the context menu with content and displays it correctly
+     * depending on the mouse position
+     *
+     * @param {array} items The data that will be put in the menu
+     * @param {Number} level The depth of the context menu
+     */
+    ContextMenu.drawMenu = function (items, level) {
+        var elements = '';
+        $.each(items, function (key, value) {
+            if (value.type === 'item') {
+                elements += ContextMenu.drawActionItem(value);
+            } else if (value.type === 'divider') {
+                elements += '<a class="list-group-item list-group-item-divider"></a>';
+            } else if (value.type === 'submenu' || value.childItems) {
+                elements += '<a class="list-group-item list-group-item-submenu"><span class="list-group-item-icon">' + value.icon + '</span> ' + value.label + '&nbsp;&nbsp;<span class="fa fa-caret-right"></span></a>';
+                var childElements = ContextMenu.drawMenu(value.childItems, 1);
+                elements += '<div class="context-menu contentMenu' + (level + 1) + '" style="display:none;"><div class="list-group">' + childElements + '</div></div>';
+
+            }
+        });
+        return elements;
+    };
+
+    ContextMenu.drawActionItem = function (value) {
+        var attributes = value.additionalAttributes || [];
+        $attributesString = '';
+        for (var attribute in attributes) {
+            $attributesString += ' ' + attribute + '="' + attributes[attribute] + '"';
+        }
+
+        return '<a class="list-group-item"'
+            + ' data-callback-action="' + value.callbackAction + '"'
+            + $attributesString + '><span class="list-group-item-icon">' + value.icon + '</span> ' + value.label + '</a>';
+    };
+    /**
+     * event handler function that saves the
+     * actual position of the mouse
+     * in the context menu object
+     *
+     * @param {Event} event The event object
+     */
+    ContextMenu.storeMousePositionEvent = function (event) {
+        ContextMenu.mousePos.X = event.pageX;
+        ContextMenu.mousePos.Y = event.pageY;
+        ContextMenu.mouseOutFromMenu('#contentMenu0');
+        ContextMenu.mouseOutFromMenu('#contentMenu1');
+    };
+
+    /**
+     * hides a visible menu if the mouse has moved outside
+     * of the object
+     *
+     * @param {Object} obj The object to hide
+     */
+    ContextMenu.mouseOutFromMenu = function (obj) {
+        var $element = $(obj);
+
+        if ($element.length > 0 && $element.is(':visible') && !this.within($element, this.mousePos.X, this.mousePos.Y)) {
+            this.hide($element);
+        } else if ($element.length > 0 && $element.is(':visible')) {
+            this.delayContextMenuHide = true;
+        }
+    };
+
+    /**
+     *
+     * @param {Object} $element
+     * @param {Number} x
+     * @param {Number} y
+     * @returns {Boolean}
+     */
+    ContextMenu.within = function ($element, x, y) {
+        var offset = $element.offset();
+        return (
+            y >= offset.top &&
+            y < offset.top + $element.height() &&
+            x >= offset.left &&
+            x < offset.left + $element.width()
+        );
+    };
+
+    /**
+     * hides a context menu
+     *
+     * @param {Object} obj The context menu object to hide
+     */
+    ContextMenu.hide = function (obj) {
+        this.delayContextMenuHide = false;
+        window.setTimeout(function () {
+            if (!ContextMenu.delayContextMenuHide) {
+                $(obj).hide();
+            }
+        }, 500);
+    };
+
+    /**
+     * hides all context menus
+     */
+    ContextMenu.hideAll = function () {
+        this.hide('#contentMenu0');
+        this.hide('#contentMenu1');
+    };
+
+    /**
+     * manipulates the DOM to add the divs needed for context menu the bottom of the <body>-tag
+     */
+    ContextMenu.initializeContextMenuContainer = function () {
+        if ($('#contentMenu0').length === 0) {
+            var code = '<div id="contentMenu0" class="context-menu"></div><div id="contentMenu1" class="context-menu" style="display: block;"></div>';
+            $('body').append(code);
+        }
+    };
+
+    ContextMenu.initializeEvents();
+
+    return ContextMenu;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js
new file mode 100644 (file)
index 0000000..4593927
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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!
+ */
+
+/**
+ * Module: TYPO3/CMS/Backend/ContextMenuActions
+ * Click menu actions for db records including tt_content and pages
+ */
+define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], function ($, Modal, Severity) {
+    /**
+     *
+     * @exports TYPO3/CMS/Backend/ContextMenuActions
+     */
+    var ContextMenuActions = {};
+
+    ContextMenuActions.getReturnUrl = function () {
+       return top.rawurlencode(top.list_frame.document.location.pathname + top.list_frame.document.location.search);
+    };
+
+    ContextMenuActions.editRecord = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.FormEngine.moduleUrl + '&edit[' + table + '][' + uid + ']=edit&returnUrl=' + ContextMenuActions.getReturnUrl()
+        );
+    };
+
+    ContextMenuActions.viewRecord = function (table, uid) {
+        var $viewUrl = $(this).data('preview-url');
+        if ($viewUrl) {
+            var previewWin = window.open($viewUrl, 'newTYPO3frontendWindow');
+            previewWin.focus();
+        }
+    };
+
+    ContextMenuActions.openInfoPopUp = function (table, uid) {
+        top.launchView(table, uid);
+    };
+
+    ContextMenuActions.mountAsTreeRoot = function (table, uid) {
+        // see actions.js -> mountAsTreeRoot
+        if (table === 'pages' && typeof top.Ext.getCmp('typo3-pagetree') !== 'undefined') {
+            var node = top.Ext.getCmp('typo3-pagetree-tree').app.getSelected();
+            // var node = top.TYPO3.Backend.NavigationContainer.PageTree.getSelected();
+            if (node === null) {
+                return false;
+            }
+
+            var useNode = {
+                attributes: {
+                    nodeData: {
+                        id: uid
+                    }
+                }
+            };
+            top.TYPO3.Components.PageTree.Actions.mountAsTreeRoot(useNode, node.ownerTree);
+        }
+    };
+
+    ContextMenuActions.newPageWizard = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.NewRecord.moduleUrl + '&id=' + uid + '&pagesOnly=1'
+        );
+    };
+
+    ContextMenuActions.newContentWizard = function (table, uid) {
+        var $wizardUrl = $(this).data('new-wizard-url');
+        if ($wizardUrl) {
+            top.TYPO3.Backend.ContentContainer.setUrl($wizardUrl);
+        }
+    };
+
+    ContextMenuActions.newRecord = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.FormEngine.moduleUrl + '&edit[' + table + '][-' + uid + ']=new&returnUrl='+ ContextMenuActions.getReturnUrl()
+        );
+    };
+
+    ContextMenuActions.openHistoryPopUp = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.RecordHistory.moduleUrl + '&element=' + table + ':' + uid
+        );
+    };
+
+    ContextMenuActions.openListModule = function (table, uid) {
+        var pageId = table === 'pages' ? uid : $(this).data('page-uid');
+        top.TYPO3.ModuleMenu.App.showModule('web_list', 'id='.pageId);
+    };
+
+    ContextMenuActions.disableRecord = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.RecordCommit.moduleUrl + '&data[' + table + '][' + uid + '][hidden]=1&prErr=1&redirect=' + ContextMenuActions.getReturnUrl()
+        );
+        top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);
+    };
+
+    ContextMenuActions.enableRecord = function (table, uid) {
+        top.TYPO3.Backend.ContentContainer.setUrl(
+            top.TYPO3.settings.RecordCommit.moduleUrl + '&data[' + table + '][' + uid + '][hidden]=0&prErr=1&redirect=' + ContextMenuActions.getReturnUrl()
+        );
+        top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);
+    };
+
+    ContextMenuActions.deleteRecord = function (table, uid) {
+        var $anchorElement = $(this);
+        var $modal = Modal.confirm(
+            $anchorElement.data('title'),
+            $anchorElement.data('message'),
+            Severity.warning, [
+                {
+                    text: $(this).data('button-close-text') || TYPO3.lang['button.cancel'] || 'Cancel',
+                    active: true,
+                    btnClass: 'btn-default',
+                    name: 'cancel'
+                },
+                {
+                    text: $(this).data('button-ok-text') || TYPO3.lang['button.delete'] || 'Delete',
+                    btnClass: 'btn-warning',
+                    name: 'delete'
+                }
+            ]);
+
+        $modal.on('button.clicked', function (e) {
+            if (e.target.name === 'delete') {
+                top.TYPO3.Backend.ContentContainer.setUrl(
+                    top.TYPO3.settings.RecordCommit.moduleUrl + '&redirect=' + ContextMenuActions.getReturnUrl() + '&cmd[' + table + '][' + uid + '][delete]=1&prErr=1'
+                );
+                if (table === 'pages' && top.TYPO3.Backend.NavigationContainer.PageTree) {
+                    top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);
+                }
+            }
+            Modal.dismiss();
+        });
+    };
+
+    ContextMenuActions.copy = function (table, uid) {
+        var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
+        url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=1';
+        $.ajax(url).always(function () {
+            top.list_frame.location.reload(true);
+        });
+    };
+
+    ContextMenuActions.clipboardRelease = function (table, uid) {
+        var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
+        url += '&CB[el][' + table + '%7C' + uid + ']=0';
+        $.ajax(url).always(function () {
+            top.list_frame.location.reload(true);
+        });
+    };
+
+    ContextMenuActions.cut = function (table, uid) {
+        var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
+        url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=0';
+        $.ajax(url).always(function () {
+            top.list_frame.location.reload(true);
+        });
+    };
+
+    /**
+     * Clear cache for given page uid
+     *
+     * @param {string} table pages table
+     * @param {int} uid of the page
+     */
+    ContextMenuActions.clearCache = function (table, uid) {
+        var url = top.TYPO3.settings.WebLayout.moduleUrl;
+        url += '&id=' + uid + '&clear_cache=1';
+        $.ajax(url);
+    };
+
+    /**
+     * Paste db record after another
+     *
+     * @param {string} table any db table except sys_file
+     * @param {int} uid of the record after which record from the cliboard will be pasted
+     */
+    ContextMenuActions.pasteAfter = function (table, uid) {
+        ContextMenuActions.pasteInto.bind($(this))(table, -uid);
+    };
+
+    /**
+     * Paste page into another page
+     *
+     * @param {string} table any db table except sys_file
+     * @param {int} uid of the record after which record from the cliboard will be pasted
+     */
+    ContextMenuActions.pasteInto = function (table, uid) {
+        var $anchorElement = $(this);
+        var title = $anchorElement.data('title');
+        var performPaste = function () {
+            var url = '&CB[paste]=' + table + '%7C' + uid
+                + '&CB[pad]=normal&prErr=1&uPT=1'
+                + '&redirect=' + ContextMenuActions.getReturnUrl();
+
+            top.TYPO3.Backend.ContentContainer.setUrl(
+                top.TYPO3.settings.RecordCommit.moduleUrl + url
+            );
+            if (table === 'pages' && top.TYPO3.Backend.NavigationContainer.PageTree) {
+                top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree.defer(500);
+            }
+        };
+        if (!$anchorElement.data('title')) {
+            performPaste();
+            return;
+        }
+        var $modal = Modal.confirm(
+            $anchorElement.data('title'),
+            $anchorElement.data('message'),
+            Severity.warning, [
+                {
+                    text: $(this).data('button-close-text') || TYPO3.lang['button.cancel'] || 'Cancel',
+                    active: true,
+                    btnClass: 'btn-default',
+                    name: 'cancel'
+                },
+                {
+                    text: $(this).data('button-ok-text') || TYPO3.lang['button.ok'] || 'OK',
+                    btnClass: 'btn-warning',
+                    name: 'ok'
+                }
+            ]);
+
+        $modal.on('button.clicked', function (e) {
+            if (e.target.name === 'ok') {
+                performPaste();
+            }
+            Modal.dismiss();
+        });
+
+    };
+
+    return ContextMenuActions;
+});
\ No newline at end of file
index 6f48c47..9e575dd 100644 (file)
@@ -474,7 +474,7 @@ define(['jquery',
 
                                // replace file icon
                                if (data.upload[0].icon) {
 
                                // replace file icon
                                if (data.upload[0].icon) {
-                                       me.$iconCol.html('<a href="#" class="t3-js-clickmenutrigger" data-table="' + data.upload[0].id + '" data-listframe="1">' + data.upload[0].icon + '&nbsp;</span></a>');
+                                       me.$iconCol.html('<a href="#" class="t3js-contextmenutrigger" data-uid="' + data.upload[0].id + '" data-table="sys_file">' + data.upload[0].icon + '&nbsp;</span></a>');
                                }
 
                                if (me.dragUploader.irreObjectUid) {
                                }
 
                                if (me.dragUploader.irreObjectUid) {
index e976350..13074e4 100644 (file)
@@ -62,10 +62,17 @@ define(['jquery'], function($) {
        DragDrop.dropElement = function(event) {
                var dropID = DragDrop.getIdFromEvent(event);
                if ((DragDrop.dragID) && (DragDrop.dragID !== dropID)) {
        DragDrop.dropElement = function(event) {
                var dropID = DragDrop.getIdFromEvent(event);
                if ((DragDrop.dragID) && (DragDrop.dragID !== dropID)) {
-                       var parameters = 'dragDrop=' + DragDrop.table +
-                                       '&srcId=' + DragDrop.dragID +
-                                       '&dstId=' + dropID;
-                       TYPO3.ClickMenu.fetch(parameters);
+                       var dragID = DragDrop.dragID;
+                       var table = DragDrop.table;
+                       var parameters = 'table=' + table + '-drag' +
+                               '&uid=' + dragID +
+                               '&dragDrop=' + table +
+                               '&srcId=' + dragID +
+                               '&dstId=' + dropID;
+                       require(['TYPO3/CMS/Backend/ContextMenu'], function (ContextMenu) {
+                               ContextMenu.record = {table: decodeURIComponent(table), uid: decodeURIComponent(dragID)};
+                               ContextMenu.fetch(parameters);
+                       });
                }
                DragDrop.cancelDragEvent();
                return false;
                }
                DragDrop.cancelDragEvent();
                return false;
index 092ed56..8398fa8 100644 (file)
@@ -96,7 +96,6 @@ TYPO3.Components.PageTree.App = Ext.extend(Ext.Panel, {
                                autoWidth: true,
                                plugins: [new Ext.ux.state.TreePanel()],
                                commandProvider: TYPO3.Components.PageTree.Actions,
                                autoWidth: true,
                                plugins: [new Ext.ux.state.TreePanel()],
                                commandProvider: TYPO3.Components.PageTree.Actions,
-                               contextMenuProvider: TYPO3.Components.PageTree.ContextMenuDataProvider,
                                treeDataProvider: TYPO3.Components.PageTree.DataProvider,
                                monitorResize: true,
                                app: this,
                                treeDataProvider: TYPO3.Components.PageTree.DataProvider,
                                monitorResize: true,
                                app: this,
@@ -119,7 +118,6 @@ TYPO3.Components.PageTree.App = Ext.extend(Ext.Panel, {
                                autoScroll: true,
                                autoHeight: false,
                                commandProvider: TYPO3.Components.PageTree.Actions,
                                autoScroll: true,
                                autoHeight: false,
                                commandProvider: TYPO3.Components.PageTree.Actions,
-                               contextMenuProvider: TYPO3.Components.PageTree.ContextMenuDataProvider,
                                treeDataProvider: TYPO3.Components.PageTree.DataProvider,
                                app: this
                        }).hide();
                                treeDataProvider: TYPO3.Components.PageTree.DataProvider,
                                app: this
                        }).hide();
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/contextmenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/contextmenu.js
deleted file mode 100644 (file)
index dd14834..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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!
- */
-Ext.namespace('TYPO3.Components.PageTree');
-
-/**
- * @class TYPO3.Components.PageTree.ContextMenu
- *
- * Context menu implementation
- *
- * @namespace TYPO3.Components.PageTree
- * @extends Ext.menu.Menu
- */
-TYPO3.Components.PageTree.ContextMenu = Ext.extend(Ext.menu.Menu, {
-       /**
-        * Context menu node
-        *
-        * @cfg {Ext.tree.TreeNode}
-        */
-       node: null,
-
-       /**
-        * Page Tree
-        *
-        * @cfg {TYPO3.Components.PageTree.Tree}
-        */
-       pageTree: null,
-
-       /**
-        * Component Id
-        *
-        * @type {String}
-        */
-       id: 'typo3-pagetree-contextmenu',
-
-       /**
-        * Parent clicks should be ignored
-        *
-        * @type {Boolean}
-        */
-       ignoreParentClicks: true,
-
-       /**
-        * Listeners
-        *
-        * The itemclick event triggers the configured single click action
-        */
-       listeners: {
-               itemclick: {
-                       fn: function (item) {
-                               if (this.pageTree.commandProvider[item.callbackAction]) {
-                                       if (item.parentMenu.pageTree.stateHash) {
-                                               fsMod.recentIds['web'] = item.parentMenu.node.attributes.nodeData.id;
-                                               item.parentMenu.pageTree.stateHash['lastSelectedNode'] = item.parentMenu.node.id;
-                                       }
-
-                                       this.pageTree.commandProvider[item.callbackAction](
-                                               item.parentMenu.node,
-                                               item.parentMenu.pageTree,
-                                               item
-                                       );
-                               }
-                       }
-               }
-       },
-
-       /**
-        * Fills the menu with the actions
-        *
-        * @param {Ext.tree.TreeNode} node
-        * @param {TYPO3.Components.PageTree.Tree} pageTree
-        * @param {Object} contextMenuConfiguration
-        * @return {void}
-        */
-       fill: function(node, pageTree, contextMenuConfiguration) {
-               this.node = node;
-               this.pageTree = pageTree;
-
-               var components = this.preProcessContextMenuConfiguration(contextMenuConfiguration);
-               if (components.length) {
-                       for (var component in components) {
-                               if (components[component] === '-') {
-                                       this.addSeparator();
-                               } else if (typeof components[component] === 'object') {
-                                       this.addItem(components[component]);
-                               }
-                       }
-               }
-       },
-
-       /**
-        * Parses the context menu actions array recursively and generates the
-        * components for the context menu including separators/dividers and sub menus
-        *
-        * @param {Object} contextMenuConfiguration
-        * @param {int} level
-        * @return {Object}
-        */
-       preProcessContextMenuConfiguration: function(contextMenuConfiguration, level) {
-               level = level || 0;
-               if (level > 5) {
-                       return [];
-               }
-
-               var components = [];
-               var index = 0;
-
-               var modulesInsideGroup = false;
-               var subMenus = 0;
-               for (var singleAction in contextMenuConfiguration) {
-                       if (contextMenuConfiguration[singleAction]['type'] === 'submenu') {
-                               var subMenuComponents = this.preProcessContextMenuConfiguration(
-                                       contextMenuConfiguration[singleAction]['childActions'],
-                                       level + 1
-                               );
-
-                               if (subMenuComponents.length) {
-                                       var subMenu = new TYPO3.Components.PageTree.ContextMenu({
-                                               id: this.id + '-sub' + ++subMenus,
-                                               items: subMenuComponents,
-                                               node: this.node,
-                                               pageTree: this.pageTree
-                                       });
-
-                                       components[index++] = {
-                                               text: contextMenuConfiguration[singleAction]['label'],
-                                               cls: 'contextMenu-subMenu',
-                                               menu: subMenu,
-                                               icon: contextMenuConfiguration[singleAction]['icon']
-                                       };
-                               }
-                       } else if (contextMenuConfiguration[singleAction]['type'] === 'divider') {
-                               if (modulesInsideGroup) {
-                                       components[index++] = '-';
-                                       modulesInsideGroup = false;
-                               }
-                       } else {
-                               modulesInsideGroup = true;
-
-                               if (typeof contextMenuConfiguration[singleAction] === 'object') {
-                                       var component = {
-                                               'text': contextMenuConfiguration[singleAction]['label'],
-                                               'icon': contextMenuConfiguration[singleAction]['icon'],
-                                               'callbackAction': contextMenuConfiguration[singleAction]['callbackAction'],
-                                               'customAttributes': contextMenuConfiguration[singleAction]['customAttributes']
-                                       };
-                                       component.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(
-                                               '<a id="{id}" class="{cls}" hidefocus="true" unselectable="on" href="{href}">',
-                                                       '<span class="x-menu-item-icon" unselectable="on">{icon}</span>',
-                                                       '<span class="x-menu-item-text">{text}</span>',
-                                               '</a>'
-                                       );
-
-                                       components[index++] = component;
-                               }
-                       }
-               }
-
-                       // remove divider if it's the last item of the context menu
-               if (components[components.length - 1] === '-') {
-                       components[components.length - 1] = '';
-               }
-
-               return components;
-       }
-});
-
-// XTYPE Registration
-Ext.reg('TYPO3.Components.PageTree.ContextMenu', TYPO3.Components.PageTree.ContextMenu);
index a649b22..5f9439d 100644 (file)
@@ -4,7 +4,6 @@ filteringtree.js
 nodeui.js
 deletiondropzone.js
 toppanel.js
 nodeui.js
 deletiondropzone.js
 toppanel.js
-contextmenu.js
 actions.js
 Ext.ux.state.TreePanel.js
 app.js
 actions.js
 Ext.ux.state.TreePanel.js
 app.js
index 78d850f..21ca826 100644 (file)
@@ -55,7 +55,7 @@ TYPO3.Components.PageTree.PageTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
                        '<span class="x-tree-node-indent">', this.indentMarkup, "</span>",
                        '<img alt="" src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
 //            '<img alt="" src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
                        '<span class="x-tree-node-indent">', this.indentMarkup, "</span>",
                        '<img alt="" src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
 //            '<img alt="" src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
-                       a.spriteIconCode, // TYPO3: add sprite icon code
+                       '<span class="t3js-contextmenutrigger" data-table="pages" data-uid="' + a.nodeData.id + '" data-context="tree" >' + a.spriteIconCode + '</span>', // TYPO3: add sprite icon code
                        (a.nodeData.stopPageTree ? '<span class="text-danger">+</span>' : ''),
                        cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
                        '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
                        (a.nodeData.stopPageTree ? '<span class="text-danger">+</span>' : ''),
                        cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
                        '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
@@ -86,16 +86,6 @@ TYPO3.Components.PageTree.PageTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
                }
                this.anchor = cs[index];
                this.textNode = cs[index].firstChild;
                }
                this.anchor = cs[index];
                this.textNode = cs[index].firstChild;
-
-                       // TYPO3: call the context menu on a single click (Beware of drag&drop!)
-               if (!TYPO3.Components.PageTree.Configuration.disableIconLinkToContextmenu
-                       || TYPO3.Components.PageTree.Configuration.disableIconLinkToContextmenu === '0'
-               ) {
-                       Ext.fly(this.iconNode).on('click', function(event) {
-                               this.getOwnerTree().fireEvent('contextmenu', this, event);
-                               event.stopEvent();
-                       }, n);
-               }
        },
 
        /**
        },
 
        /**
index abcbd0d..c9d2f44 100644 (file)
@@ -92,13 +92,6 @@ TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
        commandProvider : null,
 
        /**
        commandProvider : null,
 
        /**
-        * Context menu provider
-        *
-        * @cfg {Object}
-        */
-       contextMenuProvider: null,
-
-       /**
         * Id of the deletion drop zone if any
         *
         * @cfg {String}
         * Id of the deletion drop zone if any
         *
         * @cfg {String}
@@ -180,6 +173,11 @@ TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
                                        return false;
                                }
 
                                        return false;
                                }
 
+                               if (event.target.tagName === 'IMG') {
+                                       // Stop this event if click event was triggered via the node icon to prevent loading the action of the node
+                                       return false;
+                               }
+
                                this.clicksRegistered = 0;
                                if (this.commandProvider.singleClick) {
                                        this.commandProvider.singleClick(node, this);
                                this.clicksRegistered = 0;
                                if (this.commandProvider.singleClick) {
                                        this.commandProvider.singleClick(node, this);
@@ -198,12 +196,20 @@ TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
                        // prevents label edit on a selected node
                beforeclick: {
                        fn: function(node, event) {
                        // prevents label edit on a selected node
                beforeclick: {
                        fn: function(node, event) {
-                               if (!this.clicksRegistered && this.getSelectionModel().isSelected(node)) {
-                                       node.fireEvent('click', node, event);
+                               if (event.target.tagName === 'IMG') {
+                                       // Node icon was clicked
+                                       // This is a workaround to prevent TreeEditor being triggered, which also requires a patch within TreeEditor
+                                       node.attributes.editable = false;
+                               } else {
+                                       node.attributes.editable = true;
+
+                                       if (!this.clicksRegistered && this.getSelectionModel().isSelected(node)) {
+                                               node.fireEvent('click', node, event);
+                                               ++this.clicksRegistered;
+                                               return false;
+                                       }
                                        ++this.clicksRegistered;
                                        ++this.clicksRegistered;
-                                       return false;
                                }
                                }
-                               ++this.clicksRegistered;
                        }
                }
        },
                        }
                }
        },
@@ -230,10 +236,6 @@ TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
                        this.enableDragAndDrop();
                }
 
                        this.enableDragAndDrop();
                }
 
-               if (this.contextMenuProvider) {
-                       this.enableContextMenu();
-               }
-
                TYPO3.Components.PageTree.Tree.superclass.initComponent.apply(this, arguments);
        },
 
                TYPO3.Components.PageTree.Tree.superclass.initComponent.apply(this, arguments);
        },
 
@@ -321,45 +323,6 @@ TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
        },
 
        /**
        },
 
        /**
-        * Enables the context menu feature
-        *
-        * return {void}
-        */
-       enableContextMenu: function() {
-               this.contextMenu = new TYPO3.Components.PageTree.ContextMenu();
-
-               this.on('contextmenu', function(node, event) {
-                       this.openContextMenu(node, event);
-               });
-       },
-
-       /**
-        * Open a context menu for the given node
-        *
-        * @param {Ext.tree.TreeNode} node
-        * @param {Ext.EventObject} event
-        * return {void}
-        */
-       openContextMenu: function(node, event) {
-               var attributes = Ext.apply(node.attributes.nodeData, {
-                       t3ContextInfo: node.ownerTree.t3ContextInfo
-               });
-
-               this.contextMenuProvider.getActionsForNodeArray(
-                       attributes,
-                       function(configuration) {
-                               this.contextMenu.removeAll();
-                               this.contextMenu.fill(node, this, configuration);
-                               if (this.contextMenu.items.length) {
-                                       this.contextMenu.showAt(event.getXY());
-
-                               }
-                       },
-                       this
-               );
-       },
-
-       /**
         * Initialize the inline editor for the given tree.
         *
         * @return {void}
         * Initialize the inline editor for the given tree.
         *
         * @return {void}
diff --git a/typo3/sysext/beuser/Classes/ContextMenu/ItemProvider.php b/typo3/sysext/beuser/Classes/ContextMenu/ItemProvider.php
new file mode 100644 (file)
index 0000000..ba1b896
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Beuser\ContextMenu;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\ContextMenu\ItemProviders\PageProvider;
+
+class ItemProvider extends PageProvider
+{
+    /**
+     * @var array
+     */
+    protected $itemsConfiguration = [
+        'permissions' => [
+            'type' => 'item',
+            'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_perms',
+            'iconIdentifier' => 'status-status-locked',
+            'callbackAction' => 'openPermissionsModule'
+        ],
+    ];
+
+    /**
+     * @param string $itemName
+     * @param string $type
+     * @return bool
+     */
+    protected function canRender(string $itemName, string $type): bool
+    {
+        if (in_array($itemName, $this->disabledItems, true)) {
+            return false;
+        }
+        return $this->canShowPermissionsModule();
+    }
+
+    /**
+     * @param array $items
+     * @return array
+     */
+    public function addItems(array $items): array
+    {
+        $this->initDisabledItems();
+        $localItems = $this->prepareItems($this->itemsConfiguration);
+        if (isset($items['more']['childItems'])) {
+            $items['more']['childItems'] = $items['more']['childItems'] + $localItems;
+        } else {
+            $items += $localItems;
+        }
+        return $items;
+    }
+
+    /**
+     * This priority should be lower than priority of the PageProvider, so it's evaluated after the PageProvider
+     *
+     * @return int
+     */
+    public function getPriority(): int
+    {
+        return 60;
+    }
+
+    /**
+     * @param string $itemName
+     * @return array
+     */
+    protected function getAdditionalAttributes(string $itemName): array
+    {
+        return ['data-callback-module' => 'TYPO3/CMS/Beuser/ContextMenuActions'];
+    }
+
+    /**
+     * Checks if the page is allowed to show permission module
+     *
+     * @return bool
+     */
+    protected function canShowPermissionsModule(): bool
+    {
+        return $this->canBeEdited() && $this->backendUser->check('modules', 'system_BeuserTxPermission');
+    }
+}
diff --git a/typo3/sysext/beuser/Classes/Hook/BackendControllerHook.php b/typo3/sysext/beuser/Classes/Hook/BackendControllerHook.php
new file mode 100644 (file)
index 0000000..fe1a63e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+namespace TYPO3\CMS\Beuser\Hook;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Controller\BackendController;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * This class adds import export related JavaScript to the backend
+ */
+class BackendControllerHook
+{
+    /**
+     * Adds beuser permission specific JavaScript
+     *
+     * @param array $configuration
+     * @param BackendController $backendController
+     * @return void
+     */
+    public function addJavaScript(array $configuration, BackendController $backendController)
+    {
+        $this->getPageRenderer()->addInlineSetting('AccessPermissions', 'moduleUrl', BackendUtility::getModuleUrl('system_BeuserTxPermission'));
+    }
+
+    /**
+     * @return PageRenderer
+     */
+    protected function getPageRenderer()
+    {
+        return GeneralUtility::makeInstance(PageRenderer::class);
+    }
+}
index 56b003f..451d6bd 100644 (file)
@@ -2,7 +2,7 @@
        loadJQuery="true"
        jQueryNamespace="none"
        includeRequireJsModules="{
        loadJQuery="true"
        jQueryNamespace="none"
        includeRequireJsModules="{
-               0:'TYPO3/CMS/Backend/ClickMenu'
+               0:'TYPO3/CMS/Backend/ContextMenu'
        }"
 />
 
        }"
 />
 
index 348eec4..040a939 100644 (file)
@@ -4,7 +4,7 @@
 
 <tr>
        <td>
 
 <tr>
        <td>
-               <a href="#" class="t3-js-clickmenutrigger" data-table="be_users" data-uid="{backendUser.uid}" data-listframe="1" title="{f:if(condition: '{backendUser.description}', then: '{backendUser.description} ')}(id={backendUser.uid})">
+               <a href="#" class="t3js-contextmenutrigger" data-table="be_users" data-uid="{backendUser.uid}" title="{f:if(condition: '{backendUser.description}', then: '{backendUser.description} ')}(id={backendUser.uid})">
                        <be:avatar backendUser="{backendUser.uid}" showIcon="TRUE" />
                </a>
        </td>
                        <be:avatar backendUser="{backendUser.uid}" showIcon="TRUE" />
                </a>
        </td>
index 78bca57..0c4100b 100644 (file)
@@ -7,7 +7,7 @@
                <f:if condition="{it.isFirst}">
                        <f:then>
                                <td>
                <f:if condition="{it.isFirst}">
                        <f:then>
                                <td>
-                                       <a href="#" class="t3-js-clickmenutrigger" data-table="be_users" data-uid="{onlineUser.backendUser.uid}" data-listframe="1" title="{f:if(condition: '{onlineUser.backendUser.description}', then: '{onlineUser.backendUser.description} ')}(id={onlineUser.backendUser.uid})">
+                                       <a href="#" class="t3js-contextmenutrigger" data-table="be_users" data-uid="{onlineUser.backendUser.uid}" title="{f:if(condition: '{onlineUser.backendUser.description}', then: '{onlineUser.backendUser.description} ')}(id={onlineUser.backendUser.uid})">
                                                <be:avatar backendUser="{onlineUser.backendUser.uid}" showIcon="true" />
                                        </a>
                                </td>
                                                <be:avatar backendUser="{onlineUser.backendUser.uid}" showIcon="true" />
                                        </a>
                                </td>
index 813ea0d..f49d8b9 100644 (file)
@@ -3,7 +3,7 @@
 
 <tr>
        <td class="col-icon">
 
 <tr>
        <td class="col-icon">
-               <a href="#" class="t3-js-clickmenutrigger" data-table="be_groups" data-uid="{backendUserGroup.uid}" data-listframe="1" title="{f:if(condition: '{backendUserGroup.description}', then: '{backendUserGroup.description} ')}(id={backendUserGroup.uid})">
+               <a href="#" class="t3js-contextmenutrigger" data-table="be_groups" data-uid="{backendUserGroup.uid}" title="{f:if(condition: '{backendUserGroup.description}', then: '{backendUserGroup.description} ')}(id={backendUserGroup.uid})">
                        <bu:spriteIconForRecord table="be_groups" object="{backendUserGroup}" />
                </a>
        </td>
                        <bu:spriteIconForRecord table="be_groups" object="{backendUserGroup}" />
                </a>
        </td>
index d7d383c..0bdfe41 100644 (file)
@@ -16,7 +16,7 @@
                                        <th></th>
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <th>
                                        <th></th>
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <th>
-                                                       <a href="#" class="t3-js-clickmenutrigger" data-table="be_users" data-uid="{compareUser.uid}" data-listframe="1" title="id={compareUser.uid}">
+                                                       <a href="#" class="t3js-contextmenutrigger" data-table="be_users" data-uid="{compareUser.uid}" title="id={compareUser.uid}">
                                                                <be:avatar backendUser="{compareUser.uid}" showIcon="true" />
                                                        </a>
                                                        {compareUser.userName}
                                                                <be:avatar backendUser="{compareUser.uid}" showIcon="true" />
                                                        </a>
                                                        {compareUser.userName}
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <td>
                                                        <f:for each="{compareUser.BackendUserGroups}" as="backendUserGroup">
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <td>
                                                        <f:for each="{compareUser.BackendUserGroups}" as="backendUserGroup">
-                                                               <a href="#" class="t3-js-clickmenutrigger" data-table="be_groups" data-uid="{backendUserGroup.uid}" data-listframe="1" title="id={backendUserGroup.uid}">
+                                                               <a href="#" class="t3js-contextmenutrigger" data-table="be_groups" data-uid="{backendUserGroup.uid}" title="id={backendUserGroup.uid}">
                                                                        <bu:spriteIconForRecord table="be_users" object="{backendUserGroup}" />
                                                                </a>
                                                                {backendUserGroup.title}<br />
                                                                        <bu:spriteIconForRecord table="be_users" object="{backendUserGroup}" />
                                                                </a>
                                                                {backendUserGroup.title}<br />
index 335a11a..97ad80b 100644 (file)
@@ -21,7 +21,7 @@
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <tr>
                                                        <td>
                                        <f:for each="{compareUserList}" as="compareUser">
                                                <tr>
                                                        <td>
-                                                               <a href="#" class="t3-js-clickmenutrigger" data-table="be_users" data-uid="{compareUser.uid}" data-listframe="1" title="id={compareUser.uid}">
+                                                               <a href="#" class="t3js-contextmenutrigger" data-table="be_users" data-uid="{compareUser.uid}" title="id={compareUser.uid}">
                                                                        <be:avatar backendUser="{compareUser.uid}" showIcon="TRUE" />
                                                                </a>
                                                        </td>
                                                                        <be:avatar backendUser="{compareUser.uid}" showIcon="TRUE" />
                                                                </a>
                                                        </td>
diff --git a/typo3/sysext/beuser/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/beuser/Resources/Public/JavaScript/ContextMenuActions.js
new file mode 100644 (file)
index 0000000..b8e14f9
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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!
+ */
+
+/**
+ * Module: TYPO3/CMS/Beuser/ContextMenuActions
+ *
+ * JavaScript to handle permissions module from context menu
+ * @exports TYPO3/CMS/Beuser/ContextMenuActions
+ */
+define(function () {
+    'use strict';
+
+    /**
+     * @exports TYPO3/CMS/Beuser/ContextMenuActions
+     */
+    var ContextMenuActions = {};
+
+    /**
+     * Open permission module for given uid
+     *
+     * @param {string} table
+     * @param {int} uid of the page
+     */
+    ContextMenuActions.openPermissionsModule = function (table, uid) {
+        if (table === 'pages') {
+            top.TYPO3.Backend.ContentContainer.setUrl(
+                top.TYPO3.settings.AccessPermissions.moduleUrl +
+                    '&id=' + uid +
+                    '&tx_beuser_system_beusertxpermission[id]=' + uid +
+                    '&tx_beuser_system_beusertxpermission[action]=edit' +
+                    '&tx_beuser_system_beusertxpermission[controller]=Permission'
+            );
+        }
+    };
+
+    return ContextMenuActions;
+});
index 2bcbf61..eebb392 100644 (file)
@@ -2,3 +2,5 @@
 defined('TYPO3_MODE') or die();
 
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][] = \TYPO3\CMS\Beuser\Hook\SwitchBackUserHook::class . '->switchBack';
 defined('TYPO3_MODE') or die();
 
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][] = \TYPO3\CMS\Beuser\Hook\SwitchBackUserHook::class . '->switchBack';
+
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructPostProcess'][] = \TYPO3\CMS\Beuser\Hook\BackendControllerHook::class . '->addJavaScript';
index a152b82..3fb949c 100644 (file)
@@ -35,4 +35,6 @@ if (TYPO3_MODE === 'BE') {
             'navigationComponentId' => 'typo3-pagetree'
         ]
     );
             'navigationComponentId' => 'typo3-pagetree'
         ]
     );
+
+    $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1486418730] = \TYPO3\CMS\Beuser\ContextMenu\ItemProvider::class;
 }
 }
index 2ad8ab6..158306c 100644 (file)
@@ -540,10 +540,6 @@ class Bootstrap
                 'TYPO3.Components.PageTree.Commands',
                 \TYPO3\CMS\Backend\Tree\Pagetree\ExtdirectTreeCommands::class
             );
                 'TYPO3.Components.PageTree.Commands',
                 \TYPO3\CMS\Backend\Tree\Pagetree\ExtdirectTreeCommands::class
             );
-            ExtensionManagementUtility::registerExtDirectComponent(
-                'TYPO3.Components.PageTree.ContextMenuDataProvider',
-                \TYPO3\CMS\Backend\ContextMenu\Pagetree\Extdirect\ContextMenuConfiguration::class
-            );
         }
         return $this;
     }
         }
         return $this;
     }
index 1891113..e2af156 100644 (file)
@@ -797,243 +797,17 @@ return [
 
                        options.contextMenu {
                                table {
 
                        options.contextMenu {
                                table {
-                                       virtual_root {
+                                       pages {
                                                disableItems =
                                                disableItems =
-
-                                               items {
-                                                       100 = ITEM
-                                                       100 {
-                                                               name = history
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history
-                                                               iconName = actions-document-history-open
-                                                               displayCondition = canShowHistory != 0
-                                                               callbackAction = openHistoryPopUp
-                                                       }
-                                               }
+                                               tree.disableItems =
                                        }
                                        }
-
-                                       pages_root {
+                                       sys_file {
                                                disableItems =
                                                disableItems =
-
-                                               items {
-                                                       100 = ITEM
-                                                       100 {
-                                                               name = view
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view
-                                                               iconName = actions-document-view
-                                                               displayCondition = canBeViewed != 0
-                                                               callbackAction = viewPage
-                                                       }
-
-                                                       200 = ITEM
-                                                       200 {
-                                                               name = new
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new
-                                                               iconName = actions-page-new
-                                                               displayCondition = canCreateNewPages != 0
-                                                               callbackAction = newPageWizard
-                                                       }
-
-                                                       300 = DIVIDER
-
-                                                       400 = ITEM
-                                                       400 {
-                                                               name = history
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history
-                                                               iconName = actions-document-history-open
-                                                               displayCondition = canShowHistory != 0
-                                                               callbackAction = openHistoryPopUp
-                                                       }
-                                               }
+                                               tree.disableItems =
                                        }
                                        }
-
-                                       pages {
+                                       sys_filemounts {
                                                disableItems =
                                                disableItems =
-
-                                               items {
-                                                       100 = ITEM
-                                                       100 {
-                                                               name = view
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view
-                                                               iconName = actions-document-view
-                                                               displayCondition = canBeViewed != 0
-                                                               callbackAction = viewPage
-                                                       }
-
-                                                       200 = DIVIDER
-
-                                                       300 = ITEM
-                                                       300 {
-                                                               name = disable
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable
-                                                               iconName = actions-edit-hide
-                                                               displayCondition = getRecord|hidden = 0 && canBeDisabledAndEnabled != 0
-                                                               callbackAction = disablePage
-                                                       }
-
-                                                       400 = ITEM
-                                                       400 {
-                                                               name = enable
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enable
-                                                               iconName = actions-edit-unhide
-                                                               displayCondition = getRecord|hidden = 1 && canBeDisabledAndEnabled != 0
-                                                               callbackAction = enablePage
-                                                       }
-
-                                                       500 = ITEM
-                                                       500 {
-                                                               name = edit
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit
-                                                               iconName = actions-page-open
-                                                               displayCondition = canBeEdited != 0
-                                                               callbackAction = editPageProperties
-                                                       }
-
-                                                       600 = ITEM
-                                                       600 {
-                                                               name = info
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info
-                                                               iconName = actions-document-info
-                                                               displayCondition = canShowInfo != 0
-                                                               callbackAction = openInfoPopUp
-                                                       }
-
-                                                       700 = ITEM
-                                                       700 {
-                                                               name = history
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history
-                                                               iconName = actions-document-history-open
-                                                               displayCondition = canShowHistory != 0
-                                                               callbackAction = openHistoryPopUp
-                                                       }
-
-                                                       800 = DIVIDER
-
-                                                       900 = SUBMENU
-                                                       900 {
-                                                               label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copyPasteActions
-
-                                                               100 = ITEM
-                                                               100 {
-                                                                       name = new
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new
-                                                                       iconName = actions-page-new
-                                                                       displayCondition = canCreateNewPages != 0
-                                                                       callbackAction = newPageWizard
-                                                               }
-
-                                                               200 = DIVIDER
-
-                                                               300 = ITEM
-                                                               300 {
-                                                                       name = cut
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut
-                                                                       iconName = actions-edit-cut
-                                                                       displayCondition = isInCutMode = 0 && canBeCut != 0 && isMountPoint != 1
-                                                                       callbackAction = enableCutMode
-                                                               }
-
-                                                               400 = ITEM
-                                                               400 {
-                                                                       name = cut
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut
-                                                                       iconName = actions-edit-cut-release
-                                                                       displayCondition = isInCutMode = 1 && canBeCut != 0
-                                                                       callbackAction = disableCutMode
-                                                               }
-
-                                                               500 = ITEM
-                                                               500 {
-                                                                       name = copy
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy
-                                                                       iconName = actions-edit-copy
-                                                                       displayCondition = isInCopyMode = 0
-                                                                       callbackAction = enableCopyMode
-                                                               }
-
-                                                               600 = ITEM
-                                                               600 {
-                                                                       name = copy
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy
-                                                                       iconName = actions-edit-copy-release
-                                                                       displayCondition = isInCopyMode = 1
-                                                                       callbackAction = disableCopyMode
-                                                               }
-
-                                                               700 = ITEM
-                                                               700 {
-                                                                       name = pasteInto
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteinto
-                                                                       iconName = actions-document-paste-into
-                                                                       displayCondition = getContextInfo|inCopyMode = 1 || getContextInfo|inCutMode = 1 && canBePastedInto != 0
-                                                                       callbackAction = pasteIntoNode
-                                                               }
-
-                                                               800 = ITEM
-                                                               800 {
-                                                                       name = pasteAfter
-                                                                       label = LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteafter
-                                                                       iconName = actions-document-paste-after
-                                                                       displayCondition = getContextInfo|inCopyMode = 1 || getContextInfo|inCutMode = 1 && canBePastedAfter != 0
-                                                                       callbackAction = pasteAfterNode
-                                                               }
-
-                                                               900 = DIVIDER
-
-                                                               1000 = ITEM
-                                                               1000 {
-