[FEATURE] New API for the admin panel 90/55890/6
authorSusanne Moog <susanne.moog@typo3.org>
Sun, 25 Feb 2018 11:21:31 +0000 (12:21 +0100)
committerBenni Mack <benni@typo3.org>
Tue, 27 Feb 2018 15:45:55 +0000 (16:45 +0100)
The admin panel consisted of one big god class
that contained the complete rendering.

The hook to extend the admin panel only allowed to
add content but not to add new modules (with expandable
headers).

The code has been refactored as a first step for a more
flexible admin panel:

- All modules are now rendered by a class per module
- Modules have an interface
- Modules can be registered in ext_localconf (and overwritten) using
the dependency ordering service for priority
- All new classes are strictly typed

Related: #84044
Resolves: #84045
Releases: master
Change-Id: I124bb503907dcfcbd4425d6f7178b87562d2fda4
Reviewed-on: https://review.typo3.org/55890
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
15 files changed:
typo3/sysext/core/Documentation/Changelog/master/Deprecation-84045-AdminPanelHookDeprecated.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-84045-NewAdminPanelModuleAPI.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/AbstractModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/AdminPanelModuleInterface.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/CacheModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/EditModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/InfoModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/PreviewModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/AdminPanel/TsDebugModule.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/View/AdminPanelView.php
typo3/sysext/frontend/Classes/View/AdminPanelViewHookInterface.php
typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php
typo3/sysext/frontend/Tests/UnitDeprecated/View/AdminPanelViewTest.php [new file with mode: 0644]
typo3/sysext/frontend/ext_localconf.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84045-AdminPanelHookDeprecated.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84045-AdminPanelHookDeprecated.rst
new file mode 100644 (file)
index 0000000..7eee7ea
--- /dev/null
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+================================================
+Deprecation: #84045 - AdminPanel Hook Deprecated
+================================================
+
+See :issue:`84045`
+
+Description
+===========
+
+The hook `$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel']` has been deprecated along with the corresponding interface `\TYPO3\CMS\Frontend\View\AdminPanelViewHookInterface`.
+
+
+Impact
+======
+
+Using either the interface or registering the hook will result in a deprecation error and will stop working in future TYPO3 versions.
+
+
+Affected Installations
+======================
+
+Installations using the `\TYPO3\CMS\Frontend\View\AdminPanelViewHookInterface`.
+
+
+Migration
+=========
+
+Use the new admin panel module API starting with TYPO3 v9 LTS.
+
+.. index:: Frontend, FullyScanned, ext:frontend
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-84045-NewAdminPanelModuleAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-84045-NewAdminPanelModuleAPI.rst
new file mode 100644 (file)
index 0000000..bac629a
--- /dev/null
@@ -0,0 +1,33 @@
+.. include:: ../../Includes.txt
+
+===========================================
+Feature: #84045 - new AdminPanel module API
+===========================================
+
+See :issue:`84045`
+
+Description
+===========
+
+Extending the Admin Panel was only partially possible in earlier TYPO3 versions by using a hook that provided the possibility to add pure content (no new modules) as plain HTML.
+
+A new API has been introduced, providing more flexible options to add custom modules to the admin panel or replace and deactivate existing ones.
+
+
+Impact
+======
+
+Custom admin panel modules can now be registered via `$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['frontend']['adminPanelModules']`.
+
+.. code-block:: php
+
+       $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['frontend']['adminPanelModules']['yourmodulename'] = [
+           'module' => \Vendor\Package\AdminPanel\YourModule::class,
+           'after' => ['preview']
+       ]
+
+To implement a custom module your module class has to implement the `\TYPO3\CMS\Frontend\AdminPanel\AdminPanelModuleInterface`.
+
+Be aware that the `\TYPO3\CMS\Frontend\AdminPanel\AdminPanelModuleInterface` is not final yet and may change until v9 LTS.
+
+.. index:: Frontend, PHP-API, ext:frontend
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/AbstractModule.php b/typo3/sysext/frontend/Classes/AdminPanel/AbstractModule.php
new file mode 100644 (file)
index 0000000..43d9071
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/*
+ * 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\FrontendBackendUserAuthentication;
+use TYPO3\CMS\Core\Localization\LanguageService;
+
+/**
+ * Abstract base class for Core Admin Panel Modules containing helper methods
+ *
+ * @internal
+ */
+abstract class AbstractModule implements AdminPanelModuleInterface
+{
+    /**
+     * @inheritdoc
+     */
+    public function getAdditionalJavaScriptCode(): string
+    {
+        return '';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function showFormSubmitButton(): bool
+    {
+        return false;
+    }
+
+    /**
+     * Translate given key
+     *
+     * @param string $key Key for a label in the $LOCAL_LANG array of "sysext/lang/Resources/Private/Language/locallang_tsfe.xlf
+     * @param bool $convertWithHtmlpecialchars If TRUE the language-label will be sent through htmlspecialchars
+     * @return string The value for the $key
+     */
+    protected function extGetLL($key, $convertWithHtmlspecialchars = true): string
+    {
+        $labelStr = $this->getLanguageService()->getLL($key);
+        if ($convertWithHtmlspecialchars) {
+            $labelStr = htmlspecialchars($labelStr);
+        }
+        return $labelStr;
+    }
+
+    /**
+     * Returns LanguageService
+     *
+     * @return \TYPO3\CMS\Core\Localization\LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * Returns the current BE user.
+     *
+     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
+     */
+    protected function getBackendUser(): FrontendBackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/AdminPanelModuleInterface.php b/typo3/sysext/frontend/Classes/AdminPanel/AdminPanelModuleInterface.php
new file mode 100644 (file)
index 0000000..9e57b67
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/**
+ * Interface for admin panel modules registered via EXTCONF
+ *
+ * @internal until API is stable
+ */
+interface AdminPanelModuleInterface
+{
+    /**
+     * Identifier for this module,
+     * for example "preview" or "cache"
+     *
+     * @return string
+     */
+    public function getIdentifier(): string;
+
+    /**
+     * Module label
+     *
+     * @return string
+     */
+    public function getLabel(): string;
+
+    /**
+     * Module content as rendered HTML
+     *
+     * @return string
+     */
+    public function getContent(): string;
+
+    /**
+     * Does this module need a form submit?
+     *
+     * @return bool
+     */
+    public function showFormSubmitButton(): bool;
+
+    /**
+     * Additional JavaScript code for this module
+     * (you should only use vanilla JS here, as you cannot
+     * rely on the web site providing a specific framework)
+     *
+     * @return string
+     */
+    public function getAdditionalJavaScriptCode(): string;
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/CacheModule.php b/typo3/sysext/frontend/Classes/AdminPanel/CacheModule.php
new file mode 100644 (file)
index 0000000..d1d1507
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+class CacheModule extends AbstractModule implements AdminPanelModuleInterface
+{
+    /**
+     * Creates the content for the "cache" section ("module") of the Admin Panel
+     *
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     */
+    public function getContent(): string
+    {
+        $output = [];
+        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_cache']) {
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[cache_noCache]" value="0" />';
+            $output[] = '    <label for="cache_noCache">';
+            $output[] = '      <input type="checkbox" id="cache_noCache" name="TSFE_ADMIN_PANEL[cache_noCache]" value="1"' .
+                        ($this->getBackendUser()->uc['TSFE_adminConfig']['cache_noCache'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('cache_noCache');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '</div>';
+
+            $levels = $this->getBackendUser()->uc['TSFE_adminConfig']['cache_clearCacheLevels'];
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <label for="cache_clearCacheLevels">';
+            $output[] = '    ' . $this->extGetLL('cache_clearLevels');
+            $output[] = '  </label>';
+            $output[] = '  <select id="cache_clearCacheLevels" name="TSFE_ADMIN_PANEL[cache_clearCacheLevels]">';
+            $output[] = '    <option value="0"' . ((int)$levels === 0 ? ' selected="selected"' : '') . '>';
+            $output[] = '      ' . $this->extGetLL('div_Levels_0');
+            $output[] = '    </option>';
+            $output[] = '    <option value="1"' . ($levels == 1 ? ' selected="selected"' : '') . '>';
+            $output[] = '      ' . $this->extGetLL('div_Levels_1');
+            $output[] = '    </option>';
+            $output[] = '    <option value="2"' . ($levels == 2 ? ' selected="selected"' : '') . '>';
+            $output[] = '      ' . $this->extGetLL('div_Levels_2');
+            $output[] = '    </option>';
+            $output[] = '  </select>';
+            $output[] = '</div>';
+
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <input type="hidden" name="TSFE_ADMIN_PANEL[cache_clearCacheId]" value="' .
+                        $GLOBALS['TSFE']->id .
+                        '" />';
+            $output[] = '  <input class="typo3-adminPanel-btn typo3-adminPanel-btn-default" type="submit" value="' .
+                        $this->extGetLL('update') .
+                        '" />';
+            $output[] = '</div>';
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <input class="typo3-adminPanel-btn typo3-adminPanel-btn-default" type="submit" name="TSFE_ADMIN_PANEL[action][clearCache]" value="' .
+                        $this->extGetLL('cache_doit') .
+                        '" />';
+            $output[] = '</div>';
+        }
+        return implode('', $output);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIdentifier(): string
+    {
+        return 'cache';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLabel(): string
+    {
+        return $this->extGetLL('cache');
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function showFormSubmitButton(): bool
+    {
+        return true;
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/EditModule.php b/typo3/sysext/frontend/Classes/AdminPanel/EditModule.php
new file mode 100644 (file)
index 0000000..343dffb
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/*
+ * 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\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * Admin Panel Edit Module
+ */
+class EditModule extends AbstractModule implements AdminPanelModuleInterface
+{
+    /**
+     * Creates the content for the "edit" section ("module") of the Admin Panel
+     *
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     */
+    public function getContent(): string
+    {
+        $output = [];
+        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_edit']) {
+
+            // If another page module was specified, replace the default Page module with the new one
+            $newPageModule = trim((string)$this->getBackendUser()->getTSConfigVal('options.overridePageModule'));
+            $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
+
+            if (ExtensionManagementUtility::isLoaded('feedit')) {
+                $output[] = '<div class="typo3-adminPanel-form-group">';
+                $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+                $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[edit_displayFieldIcons]" value="0" />';
+                $output[] = '    <label for="edit_displayFieldIcons">';
+                $output[] = '      <input type="checkbox" id="edit_displayFieldIcons" name="TSFE_ADMIN_PANEL[edit_displayFieldIcons]" value="1"' .
+                            ($this->getBackendUser(
+                            )->uc['TSFE_adminConfig']['edit_displayFieldIcons'] ? ' checked="checked"' : '') .
+                            ' />';
+                $output[] = '      ' . $this->extGetLL('edit_displayFieldIcons');
+                $output[] = '    </label>';
+                $output[] = '  </div>';
+                $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+                $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[edit_displayIcons]" value="0" />';
+                $output[] = '    <label for="edit_displayIcons">';
+                $output[] = '      <input type="checkbox" id="edit_displayIcons" name="TSFE_ADMIN_PANEL[edit_displayIcons]" value="1"' .
+                            ($this->getBackendUser(
+                            )->uc['TSFE_adminConfig']['edit_displayIcons'] ? ' checked="checked"' : '') .
+                            ' />';
+                $output[] = '      ' . $this->extGetLL('edit_displayIcons');
+                $output[] = '    </label>';
+                $output[] = '  </div>';
+                $output[] = '</div>';
+            }
+
+            $output[] = $this->getBackendUser()->adminPanel->ext_makeToolBar();
+
+            if (!GeneralUtility::_GP('ADMCMD_view')) {
+                $onClick = '
+                    if (parent.opener && parent.opener.top && parent.opener.top.TS) {
+                        parent.opener.top.fsMod.recentIds["web"]=' .
+                           (int)$this->getTypoScriptFrontendController()->page['uid'] .
+                           ';
+                        if (parent.opener.top && parent.opener.top.nav_frame && parent.opener.top.nav_frame.refresh_nav) {
+                            parent.opener.top.nav_frame.refresh_nav();
+                        }
+                        parent.opener.top.goToModule("' .
+                           $pageModule .
+                           '");
+                        parent.opener.top.focus();
+                    } else {
+                        vHWin=window.open(' .
+                           GeneralUtility::quoteJSvalue(BackendUtility::getBackendScript()) .
+                           ',\'' .
+                           md5('Typo3Backend-' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) .
+                           '\');
+                        vHWin.focus();
+                    }
+                    return false;
+                ';
+                $output[] = '<div class="typo3-adminPanel-form-group">';
+                $output[] = '  <a class="typo3-adminPanel-btn typo3-adminPanel-btn-default" href="#" onclick="' .
+                            htmlspecialchars($onClick) .
+                            '">';
+                $output[] = '    ' . $this->extGetLL('edit_openAB');
+                $output[] = '  </a>';
+                $output[] = '</div>';
+            }
+        }
+        return implode('', $output);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIdentifier(): string
+    {
+        return 'edit';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLabel(): string
+    {
+        return $this->extGetLL('edit');
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function showFormSubmitButton(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @return TypoScriptFrontendController
+     */
+    protected function getTypoScriptFrontendController(): TypoScriptFrontendController
+    {
+        return $GLOBALS['TSFE'];
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/InfoModule.php b/typo3/sysext/frontend/Classes/AdminPanel/InfoModule.php
new file mode 100644 (file)
index 0000000..3a7f435
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/*
+ * 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\TimeTracker\TimeTracker;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * Admin Panel Info Module
+ */
+class InfoModule extends AbstractModule implements AdminPanelModuleInterface
+{
+    /**
+     * Creates the content for the "info" section ("module") of the Admin Panel
+     *
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     * @see display()
+     */
+    public function getContent(): string
+    {
+        $output = [];
+        $tsfe = $this->getTypoScriptFrontendController();
+        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_info']) {
+            $tableArr = [];
+            if ($this->getBackendUser()->adminPanel->extGetFeAdminValue('cache', 'noCache')) {
+                $theBytes = 0;
+                $count = 0;
+                if (!empty($tsfe->imagesOnPage)) {
+                    $tableArr[] = [$this->extGetLL('info_imagesOnPage'), count($tsfe->imagesOnPage), true];
+                    foreach ($GLOBALS['TSFE']->imagesOnPage as $file) {
+                        $fs = @filesize($file);
+                        $tableArr[] = [TAB . $file, GeneralUtility::formatSize($fs)];
+                        $theBytes += $fs;
+                        $count++;
+                    }
+                }
+                // Add an empty line
+                $tableArr[] = [$this->extGetLL('info_imagesSize'), GeneralUtility::formatSize($theBytes), true];
+                $tableArr[] = [
+                    $this->extGetLL('info_DocumentSize'),
+                    GeneralUtility::formatSize(strlen($tsfe->content)),
+                    true,
+                ];
+                $tableArr[] = ['', ''];
+            }
+            $tableArr[] = [$this->extGetLL('info_id'), $tsfe->id];
+            $tableArr[] = [$this->extGetLL('info_type'), $tsfe->type];
+            $tableArr[] = [$this->extGetLL('info_groupList'), $tsfe->gr_list];
+            $tableArr[] = [
+                $this->extGetLL('info_noCache'),
+                $this->extGetLL('info_noCache_' . ($tsfe->no_cache ? 'no' : 'yes')),
+            ];
+            $tableArr[] = [$this->extGetLL('info_countUserInt'), count($tsfe->config['INTincScript'] ?? [])];
+
+            if (!empty($tsfe->fe_user->user['uid'])) {
+                $tableArr[] = [$this->extGetLL('info_feuserName'), htmlspecialchars($tsfe->fe_user->user['username'])];
+                $tableArr[] = [$this->extGetLL('info_feuserId'), htmlspecialchars($tsfe->fe_user->user['uid'])];
+            }
+
+            $tableArr[] = [
+                $this->extGetLL('info_totalParsetime'),
+                $this->getTimeTracker()->getParseTime() . ' ms',
+                true,
+            ];
+            $table = '';
+            foreach ($tableArr as $key => $arr) {
+                $label = (isset($arr[2]) ? '<strong>' . $arr[0] . '</strong>' : $arr[0]);
+                $value = (string)$arr[1] !== '' ? $arr[1] : '';
+                $table .= '
+                    <tr>
+                        <td>' . $label . '</td>
+                        <td>' . htmlspecialchars((string)$value) . '</td>
+                    </tr>';
+            }
+
+            $output[] = '<div class="typo3-adminPanel-table-overflow">';
+            $output[] = '  <table class="typo3-adminPanel-table">';
+            $output[] = '    ' . $table;
+            $output[] = '  </table>';
+            $output[] = '</div>';
+        }
+
+        return implode('', $output);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIdentifier(): string
+    {
+        return 'info';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLabel(): string
+    {
+        return $this->extGetLL('info');
+    }
+
+    /**
+     * @return TypoScriptFrontendController
+     */
+    protected function getTypoScriptFrontendController(): TypoScriptFrontendController
+    {
+        return $GLOBALS['TSFE'];
+    }
+
+    /**
+     * @return TimeTracker
+     */
+    protected function getTimeTracker(): TimeTracker
+    {
+        return GeneralUtility::makeInstance(TimeTracker::class);
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/PreviewModule.php b/typo3/sysext/frontend/Classes/AdminPanel/PreviewModule.php
new file mode 100644 (file)
index 0000000..15c5415
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/*
+ * 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\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Admin Panel Preview Module
+ */
+class PreviewModule extends AbstractModule implements AdminPanelModuleInterface
+{
+
+    /**
+     * Creates the content for the "preview" section ("module") of the Admin Panel
+     *
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     * @see display()
+     * @throws \InvalidArgumentException
+     */
+    public function getContent(): string
+    {
+        $output = [];
+        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_preview']) {
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showHiddenPages]" value="0" />';
+            $output[] = '    <label for="preview_showHiddenPages">';
+            $output[] = '      <input type="checkbox" id="preview_showHiddenPages" name="TSFE_ADMIN_PANEL[preview_showHiddenPages]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['preview_showHiddenPages'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('preview_showHiddenPages');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showHiddenRecords]" value="0" />';
+            $output[] = '    <label for="preview_showHiddenRecords">';
+            $output[] = '      <input type="checkbox" id="preview_showHiddenRecords" name="TSFE_ADMIN_PANEL[preview_showHiddenRecords]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['preview_showHiddenRecords'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('preview_showHiddenRecords');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showFluidDebug]" value="0" />';
+            $output[] = '    <label for="preview_showFluidDebug">';
+            $output[] = '      <input type="checkbox" id="preview_showFluidDebug" name="TSFE_ADMIN_PANEL[preview_showFluidDebug]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['preview_showFluidDebug'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('preview_showFluidDebug');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '</div>';
+
+            // Simulate date
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <label for="preview_simulateDate">';
+            $output[] = '    ' . $this->extGetLL('preview_simulateDate');
+            $output[] = '  </label>';
+            $output[] = '  <input type="text" id="preview_simulateDate" name="TSFE_ADMIN_PANEL[preview_simulateDate]_hr" onchange="TSFEtypo3FormFieldGet(\'TSFE_ADMIN_PANEL[preview_simulateDate]\', \'datetime\', \'\', 1,0);" />';
+            // the hidden field must be placed after the _hr field to avoid the timestamp being overridden by the date string
+            $output[] = '  <input type="hidden" name="TSFE_ADMIN_PANEL[preview_simulateDate]" value="' .
+                        $this->getBackendUser()->uc['TSFE_adminConfig']['preview_simulateDate'] .
+                        '" />';
+            $output[] = '</div>';
+
+            // Frontend Usergroups
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable('fe_groups');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+            $optionCount = $queryBuilder->count('fe_groups.uid')
+                ->from('fe_groups')
+                ->from('pages')
+                ->where(
+                    $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('fe_groups.pid')),
+                    $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
+                )
+                ->execute()
+                ->fetchColumn(0);
+            if ($optionCount > 0) {
+                $result = $queryBuilder->select('fe_groups.uid', 'fe_groups.title')
+                    ->from('fe_groups')
+                    ->from('pages')
+                    ->where(
+                        $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('fe_groups.pid')),
+                        $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
+                    )
+                    ->orderBy('fe_groups.title')
+                    ->execute();
+                $output[] = '<div class="typo3-adminPanel-form-group">';
+                $output[] = '  <label for="preview_simulateUserGroup">';
+                $output[] = '    ' . $this->extGetLL('preview_simulateUserGroup');
+                $output[] = '  </label>';
+                $output[] = '  <select id="preview_simulateUserGroup" name="TSFE_ADMIN_PANEL[preview_simulateUserGroup]">';
+                $output[] = '    <option value="0">&nbsp;</option>';
+                while ($row = $result->fetch()) {
+                    $output[] = '<option value="' .
+                                (int)$row['uid'] .
+                                '" ' .
+                                ($this->getBackendUser()->uc['TSFE_adminConfig']['preview_simulateUserGroup'] ===
+                                 $row['uid'] ? ' selected="selected"' : '') .
+                                '>';
+                    $output[] = htmlspecialchars(($row['title'] . ' [' . $row['uid'] . ']'));
+                    $output[] = '</option>';
+                }
+                $output[] = '  </select>';
+                $output[] = '</div>';
+            }
+        }
+        return implode('', $output);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIdentifier(): string
+    {
+        return 'preview';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLabel(): string
+    {
+        return $this->extGetLL('preview');
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function showFormSubmitButton(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getAdditionalJavaScriptCode(): string
+    {
+        return 'TSFEtypo3FormFieldSet("TSFE_ADMIN_PANEL[preview_simulateDate]", "datetime", "", 0, 0);';
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/AdminPanel/TsDebugModule.php b/typo3/sysext/frontend/Classes/AdminPanel/TsDebugModule.php
new file mode 100644 (file)
index 0000000..a622589
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Frontend\AdminPanel;
+
+/*
+ * 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\TimeTracker\TimeTracker;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Admin Panel TypoScript Debug Module
+ */
+class TsDebugModule extends AbstractModule implements AdminPanelModuleInterface
+{
+
+    /**
+     * Creates the content for the "tsdebug" section ("module") of the Admin Panel
+     *
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     */
+    public function getContent(): string
+    {
+        $output = [];
+        $beuser = $this->getBackendUser();
+        if ($beuser->uc['TSFE_adminConfig']['display_tsdebug']) {
+            $output[] = '<div class="typo3-adminPanel-form-group">';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_tree]" value="0" />';
+            $output[] = '    <label for="tsdebug_tree">';
+            $output[] = '      <input type="checkbox" id="tsdebug_tree" name="TSFE_ADMIN_PANEL[tsdebug_tree]" value="1"' .
+                        ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_tree'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_tree');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayTimes]" value="0" />';
+            $output[] = '    <label for="tsdebug_displayTimes">';
+            $output[] = '      <input type="checkbox" id="tsdebug_displayTimes" name="TSFE_ADMIN_PANEL[tsdebug_displayTimes]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['tsdebug_displayTimes'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_displayTimes');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayMessages]" value="0" />';
+            $output[] = '    <label for="tsdebug_displayMessages">';
+            $output[] = '      <input type="checkbox" id="tsdebug_displayMessages" name="TSFE_ADMIN_PANEL[tsdebug_displayMessages]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['tsdebug_displayMessages'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_displayMessages');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_LR]" value="0" />';
+            $output[] = '    <label for="tsdebug_LR">';
+            $output[] = '      <input type="checkbox" id="tsdebug_LR" name="TSFE_ADMIN_PANEL[tsdebug_LR]" value="1"' .
+                        ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_LR'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_LR');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayContent]" value="0" />';
+            $output[] = '    <label for="tsdebug_displayContent">';
+            $output[] = '      <input type="checkbox" id="tsdebug_displayContent" name="TSFE_ADMIN_PANEL[tsdebug_displayContent]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['tsdebug_displayContent'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_displayContent');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
+            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_forceTemplateParsing]" value="0" />';
+            $output[] = '    <label for="tsdebug_forceTemplateParsing">';
+            $output[] = '      <input type="checkbox" id="tsdebug_forceTemplateParsing" name="TSFE_ADMIN_PANEL[tsdebug_forceTemplateParsing]" value="1"' .
+                        ($this->getBackendUser(
+                        )->uc['TSFE_adminConfig']['tsdebug_forceTemplateParsing'] ? ' checked="checked"' : '') .
+                        ' />';
+            $output[] = '      ' . $this->extGetLL('tsdebug_forceTemplateParsing');
+            $output[] = '    </label>';
+            $output[] = '  </div>';
+            $output[] = '</div>';
+
+            $timeTracker = $this->getTimeTracker();
+            $timeTracker->printConf['flag_tree'] = $this->getBackendUser()->adminPanel->extGetFeAdminValue('tsdebug', 'tree');
+            $timeTracker->printConf['allTime'] = $this->getBackendUser()->adminPanel->extGetFeAdminValue('tsdebug', 'displayTimes');
+            $timeTracker->printConf['flag_messages'] = $this->getBackendUser()->adminPanel->extGetFeAdminValue('tsdebug', 'displayMessages');
+            $timeTracker->printConf['flag_content'] = $this->getBackendUser()->adminPanel->extGetFeAdminValue('tsdebug', 'displayContent');
+            $output[] = $timeTracker->printTSlog();
+        }
+        return implode('', $output);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIdentifier(): string
+    {
+        return 'tsdebug';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLabel(): string
+    {
+        return $this->extGetLL('tsdebug');
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function showFormSubmitButton(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @return TimeTracker
+     */
+    protected function getTimeTracker(): TimeTracker
+    {
+        return GeneralUtility::makeInstance(TimeTracker::class);
+    }
+}
index 866735a..dda6cc7 100644 (file)
@@ -17,15 +17,16 @@ namespace TYPO3\CMS\Frontend\View;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\Frontend\AdminPanel\AdminPanelModuleInterface;
 
 /**
  * View class for the admin panel in frontend editing.
@@ -64,6 +65,13 @@ class AdminPanelView
     protected $extFeEditLoaded = false;
 
     /**
+     * Array of adminPanel modules
+     *
+     * @var AdminPanelModuleInterface[]
+     */
+    protected $modules = [];
+
+    /**
      * Constructor
      */
     public function __construct()
@@ -81,6 +89,7 @@ class AdminPanelView
         $typoScriptFrontend = $this->getTypoScriptFrontendController();
         // Setting some values based on the admin panel
         $this->extFeEditLoaded = ExtensionManagementUtility::isLoaded('feedit');
+        $this->validateSortAndInitiateModules();
         $typoScriptFrontend->forceTemplateParsing = $this->extGetFeAdminValue('tsdebug', 'forceTemplateParsing');
         $typoScriptFrontend->displayEditIcons = $this->extGetFeAdminValue('edit', 'displayIcons');
         $typoScriptFrontend->displayFieldEditIcons = $this->extGetFeAdminValue('edit', 'displayFieldIcons');
@@ -249,17 +258,18 @@ class AdminPanelView
     /**
      * @param string $key
      * @param string $content
+     * @param string $label
      *
      * @return string
      */
-    protected function getModule($key, $content)
+    protected function getModule($key, $content, $label = '')
     {
         $output = [];
 
         if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_top'] && $this->isAdminModuleEnabled($key)) {
             $output[] = '<div class="typo3-adminPanel-section typo3-adminPanel-section-' . ($this->isAdminModuleOpen($key) ? 'open' : 'closed') . '">';
             $output[] = '  <div class="typo3-adminPanel-section-title">';
-            $output[] = '    ' . $this->linkSectionHeader($key, $this->extGetLL($key));
+            $output[] = '    ' . $this->linkSectionHeader($key, $label ?: $this->extGetLL($key));
             $output[] = '  </div>';
             if ($this->isAdminModuleOpen($key)) {
                 $output[] = '<div class="typo3-adminPanel-section-body">';
@@ -283,13 +293,20 @@ class AdminPanelView
         $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_tsfe.xlf');
 
         $moduleContent = '';
-        $moduleContent .= $this->getModule('preview', $this->getPreviewModule());
-        $moduleContent .= $this->getModule('cache', $this->getCacheModule());
-        $moduleContent .= $this->getModule('edit', $this->getEditModule());
-        $moduleContent .= $this->getModule('tsdebug', $this->getTSDebugModule());
-        $moduleContent .= $this->getModule('info', $this->getInfoModule());
+
+        foreach ($this->modules as $module) {
+            if ($this->isAdminModuleOpen($module->getIdentifier())) {
+                $this->extNeedUpdate = !$this->extNeedUpdate ? $module->showFormSubmitButton() : true;
+                $this->extJSCODE .= $module->getAdditionalJavaScriptCode();
+            }
+            $moduleContent .= $this->getModule($module->getIdentifier(), $module->getContent(), $module->getLabel());
+        }
 
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel'] ?? [] as $className) {
+            trigger_error(
+                'The hook $GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_adminpanel.php\'][\'extendAdminPanel\'] is deprecated, register an AdminPanelModule instead.',
+                E_USER_DEPRECATED
+            );
             $hookObject = GeneralUtility::makeInstance($className);
             if (!$hookObject instanceof AdminPanelViewHookInterface) {
                 throw new \UnexpectedValueException($className . ' must implement interface ' . AdminPanelViewHookInterface::class, 1311942539);
@@ -419,347 +436,49 @@ class AdminPanelView
         return $out;
     }
 
-    /*****************************************************
-     * Creating sections of the Admin Panel
-     ****************************************************/
-    /**
-     * Creates the content for the "preview" section ("module") of the Admin Panel
-     *
-     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
-     * @see display()
-     */
-    protected function getPreviewModule()
-    {
-        $output = [];
-        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_preview']) {
-            $this->extNeedUpdate = true;
-
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showHiddenPages]" value="0" />';
-            $output[] = '    <label for="preview_showHiddenPages">';
-            $output[] = '      <input type="checkbox" id="preview_showHiddenPages" name="TSFE_ADMIN_PANEL[preview_showHiddenPages]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['preview_showHiddenPages'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('preview_showHiddenPages');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showHiddenRecords]" value="0" />';
-            $output[] = '    <label for="preview_showHiddenRecords">';
-            $output[] = '      <input type="checkbox" id="preview_showHiddenRecords" name="TSFE_ADMIN_PANEL[preview_showHiddenRecords]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['preview_showHiddenRecords'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('preview_showHiddenRecords');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[preview_showFluidDebug]" value="0" />';
-            $output[] = '    <label for="preview_showFluidDebug">';
-            $output[] = '      <input type="checkbox" id="preview_showFluidDebug" name="TSFE_ADMIN_PANEL[preview_showFluidDebug]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['preview_showFluidDebug'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('preview_showFluidDebug');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '</div>';
-
-            // Simulate date
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <label for="preview_simulateDate">';
-            $output[] = '    ' . $this->extGetLL('preview_simulateDate');
-            $output[] = '  </label>';
-            $output[] = '  <input type="text" id="preview_simulateDate" name="TSFE_ADMIN_PANEL[preview_simulateDate]_hr" onchange="TSFEtypo3FormFieldGet(' . GeneralUtility::quoteJSvalue('TSFE_ADMIN_PANEL[preview_simulateDate]') . ', "datetime", "", 1,0);" />';
-            // the hidden field must be placed after the _hr field to avoid the timestamp being overridden by the date string
-            $output[] = '  <input type="hidden" name="TSFE_ADMIN_PANEL[preview_simulateDate]" value="' . htmlspecialchars($this->getBackendUser()->uc['TSFE_adminConfig']['preview_simulateDate']) . '" />';
-            $output[] = '</div>';
-            $this->extJSCODE .= 'TSFEtypo3FormFieldSet("TSFE_ADMIN_PANEL[preview_simulateDate]", "datetime", "", 0, 0);';
-
-            // Frontend Usergroups
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                ->getQueryBuilderForTable('fe_groups');
-            $queryBuilder->getRestrictions()
-                ->removeAll()
-                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
-            $optionCount = $queryBuilder->count('fe_groups.uid')
-                ->from('fe_groups')
-                ->from('pages')
-                ->where(
-                    $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('fe_groups.pid')),
-                    $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
-                )
-                ->execute()
-                ->fetchColumn(0);
-            if ($optionCount > 0) {
-                $result = $queryBuilder->select('fe_groups.uid', 'fe_groups.title')
-                    ->from('fe_groups')
-                    ->from('pages')
-                    ->where(
-                        $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('fe_groups.pid')),
-                        $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
-                    )
-                    ->orderBy('fe_groups.title')
-                    ->execute();
-                $output[] = '<div class="typo3-adminPanel-form-group">';
-                $output[] = '  <label for="preview_simulateUserGroup">';
-                $output[] = '    ' . $this->extGetLL('preview_simulateUserGroup');
-                $output[] = '  </label>';
-                $output[] = '  <select id="preview_simulateUserGroup" name="TSFE_ADMIN_PANEL[preview_simulateUserGroup]">';
-                $output[] = '    <option value="0">&nbsp;</option>';
-                while ($row = $result->fetch()) {
-                    $output[] = '<option value="' . $row['uid'] . '" ' . ($this->getBackendUser()->uc['TSFE_adminConfig']['preview_simulateUserGroup'] == $row['uid'] ? ' selected="selected"' : '') . '>';
-                    $output[] = htmlspecialchars(($row['title'] . ' [' . $row['uid'] . ']'));
-                    $output[] = '</option>';
-                }
-                $output[] = '  </select>';
-                $output[] = '</div>';
-            }
-        }
-        return implode('', $output);
-    }
-
     /**
-     * Creates the content for the "cache" section ("module") of the Admin Panel
+     * Validates, sorts and initiates the registered modules
      *
-     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
-     * @see display()
+     * @throws \RuntimeException
      */
-    protected function getCacheModule()
+    protected function validateSortAndInitiateModules(): void
     {
-        $output = [];
-        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_cache']) {
-            $this->extNeedUpdate = true;
-
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[cache_noCache]" value="0" />';
-            $output[] = '    <label for="cache_noCache">';
-            $output[] = '      <input type="checkbox" id="cache_noCache" name="TSFE_ADMIN_PANEL[cache_noCache]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['cache_noCache'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('cache_noCache');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '</div>';
-
-            $levels = $this->getBackendUser()->uc['TSFE_adminConfig']['cache_clearCacheLevels'];
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <label for="cache_clearCacheLevels">';
-            $output[] = '    ' . $this->extGetLL('cache_clearLevels');
-            $output[] = '  </label>';
-            $output[] = '  <select id="cache_clearCacheLevels" name="TSFE_ADMIN_PANEL[cache_clearCacheLevels]">';
-            $output[] = '    <option value="0"' . ($levels == 0 ? ' selected="selected"' : '') . '>';
-            $output[] = '      ' . $this->extGetLL('div_Levels_0');
-            $output[] = '    </option>';
-            $output[] = '    <option value="1"' . ($levels == 1 ? ' selected="selected"' : '') . '>';
-            $output[] = '      ' . $this->extGetLL('div_Levels_1');
-            $output[] = '    </option>';
-            $output[] = '    <option value="2"' . ($levels == 2 ? ' selected="selected"' : '') . '>';
-            $output[] = '      ' . $this->extGetLL('div_Levels_2');
-            $output[] = '    </option>';
-            $output[] = '  </select>';
-            $output[] = '</div>';
-
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <input type="hidden" name="TSFE_ADMIN_PANEL[cache_clearCacheId]" value="' . (int)$GLOBALS['TSFE']->id . '" />';
-            $output[] = '  <input class="typo3-adminPanel-btn typo3-adminPanel-btn-default" type="submit" value="' . $this->extGetLL('update') . '" />';
-            $output[] = '</div>';
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <input class="typo3-adminPanel-btn typo3-adminPanel-btn-default" type="submit" name="TSFE_ADMIN_PANEL[action][clearCache]" value="' . $this->extGetLL('cache_doit') . '" />';
-            $output[] = '</div>';
-        }
-        return implode('', $output);
-    }
-
-    /**
-     * Creates the content for the "edit" section ("module") of the Admin Panel
-     *
-     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
-     * @see display()
-     */
-    protected function getEditModule()
-    {
-        $output = [];
-        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_edit']) {
-            $this->extNeedUpdate = true;
-
-            // If another page module was specified, replace the default Page module with the new one
-            $newPageModule = trim($this->getBackendUser()->getTSConfigVal('options.overridePageModule'));
-            $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
-
-            if ($this->extFeEditLoaded) {
-                $output[] = '<div class="typo3-adminPanel-form-group">';
-                $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-                $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[edit_displayFieldIcons]" value="0" />';
-                $output[] = '    <label for="edit_displayFieldIcons">';
-                $output[] = '      <input type="checkbox" id="edit_displayFieldIcons" name="TSFE_ADMIN_PANEL[edit_displayFieldIcons]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['edit_displayFieldIcons'] ? ' checked="checked"' : '') . ' />';
-                $output[] = '      ' . $this->extGetLL('edit_displayFieldIcons');
-                $output[] = '    </label>';
-                $output[] = '  </div>';
-                $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-                $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[edit_displayIcons]" value="0" />';
-                $output[] = '    <label for="edit_displayIcons">';
-                $output[] = '      <input type="checkbox" id="edit_displayIcons" name="TSFE_ADMIN_PANEL[edit_displayIcons]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['edit_displayIcons'] ? ' checked="checked"' : '') . ' />';
-                $output[] = '      ' . $this->extGetLL('edit_displayIcons');
-                $output[] = '    </label>';
-                $output[] = '  </div>';
-                $output[] = '</div>';
+        $modules = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['frontend']['adminPanelModules'] ?? [];
+        if (empty($modules)) {
+            return;
+        }
+        foreach ($modules as $identifier => $configuration) {
+            if (empty($configuration) || !is_array($configuration)) {
+                throw new \RuntimeException(
+                    'Missing configuration for module "' . $identifier . '".',
+                    1519490105
+                );
             }
-
-            $output[] = $this->ext_makeToolBar();
-
-            if (!GeneralUtility::_GP('ADMCMD_view')) {
-                $onClick = '
-                    if (parent.opener && parent.opener.top && parent.opener.top.TS) {
-                        parent.opener.top.fsMod.recentIds["web"]=' . (int)$this->getTypoScriptFrontendController()->page['uid'] . ';
-                        if (parent.opener.top && parent.opener.top.nav_frame && parent.opener.top.nav_frame.refresh_nav) {
-                            parent.opener.top.nav_frame.refresh_nav();
-                        }
-                        parent.opener.top.goToModule("' . $pageModule . '");
-                        parent.opener.top.focus();
-                    } else {
-                        vHWin=window.open(' . GeneralUtility::quoteJSvalue(BackendUtility::getBackendScript()) . ',' . GeneralUtility::quoteJSvalue(md5('Typo3Backend-' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'])) . ');
-                        vHWin.focus();
-                    }
-                    return false;
-                ';
-                $output[] = '<div class="typo3-adminPanel-form-group">';
-                $output[] = '  <a class="typo3-adminPanel-btn typo3-adminPanel-btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '">';
-                $output[] = '    ' . $this->extGetLL('edit_openAB');
-                $output[] = '  </a>';
-                $output[] = '</div>';
+            if (!is_string($configuration['module']) ||
+                empty($configuration['module']) ||
+                !class_exists($configuration['module']) ||
+                !is_subclass_of(
+                    $configuration['module'],
+                    AdminPanelModuleInterface::class
+                )) {
+                throw new \RuntimeException(
+                    'The module "' .
+                    $identifier .
+                    '" defines an invalid module class. Ensure the class exists and implements the "' .
+                    AdminPanelModuleInterface::class .
+                    '".',
+                    1519490112
+                );
             }
         }
-        return implode('', $output);
-    }
 
-    /**
-     * Creates the content for the "tsdebug" section ("module") of the Admin Panel
-     *
-     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
-     * @see display()
-     */
-    protected function getTSDebugModule()
-    {
-        $output = [];
-        $beuser = $this->getBackendUser();
-        if ($beuser->uc['TSFE_adminConfig']['display_tsdebug']) {
-            $this->extNeedUpdate = true;
-
-            $output[] = '<div class="typo3-adminPanel-form-group">';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_tree]" value="0" />';
-            $output[] = '    <label for="tsdebug_tree">';
-            $output[] = '      <input type="checkbox" id="tsdebug_tree" name="TSFE_ADMIN_PANEL[tsdebug_tree]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_tree'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_tree');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayTimes]" value="0" />';
-            $output[] = '    <label for="tsdebug_displayTimes">';
-            $output[] = '      <input type="checkbox" id="tsdebug_displayTimes" name="TSFE_ADMIN_PANEL[tsdebug_displayTimes]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_displayTimes'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_displayTimes');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayMessages]" value="0" />';
-            $output[] = '    <label for="tsdebug_displayMessages">';
-            $output[] = '      <input type="checkbox" id="tsdebug_displayMessages" name="TSFE_ADMIN_PANEL[tsdebug_displayMessages]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_displayMessages'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_displayMessages');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_LR]" value="0" />';
-            $output[] = '    <label for="tsdebug_LR">';
-            $output[] = '      <input type="checkbox" id="tsdebug_LR" name="TSFE_ADMIN_PANEL[tsdebug_LR]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_LR'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_LR');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_displayContent]" value="0" />';
-            $output[] = '    <label for="tsdebug_displayContent">';
-            $output[] = '      <input type="checkbox" id="tsdebug_displayContent" name="TSFE_ADMIN_PANEL[tsdebug_displayContent]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_displayContent'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_displayContent');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '  <div class="typo3-adminPanel-form-group-checkbox">';
-            $output[] = '    <input type="hidden" name="TSFE_ADMIN_PANEL[tsdebug_forceTemplateParsing]" value="0" />';
-            $output[] = '    <label for="tsdebug_forceTemplateParsing">';
-            $output[] = '      <input type="checkbox" id="tsdebug_forceTemplateParsing" name="TSFE_ADMIN_PANEL[tsdebug_forceTemplateParsing]" value="1"' . ($this->getBackendUser()->uc['TSFE_adminConfig']['tsdebug_forceTemplateParsing'] ? ' checked="checked"' : '') . ' />';
-            $output[] = '      ' . $this->extGetLL('tsdebug_forceTemplateParsing');
-            $output[] = '    </label>';
-            $output[] = '  </div>';
-            $output[] = '</div>';
-
-            $timeTracker = $this->getTimeTracker();
-            $timeTracker->printConf['flag_tree'] = $this->extGetFeAdminValue('tsdebug', 'tree');
-            $timeTracker->printConf['allTime'] = $this->extGetFeAdminValue('tsdebug', 'displayTimes');
-            $timeTracker->printConf['flag_messages'] = $this->extGetFeAdminValue('tsdebug', 'displayMessages');
-            $timeTracker->printConf['flag_content'] = $this->extGetFeAdminValue('tsdebug', 'displayContent');
-            $output[] = $timeTracker->printTSlog();
-        }
-        return implode('', $output);
-    }
-
-    /**
-     * Creates the content for the "info" section ("module") of the Admin Panel
-     *
-     * @return string HTML content for the section. Consists of a string with table-rows with two columns.
-     * @see display()
-     */
-    protected function getInfoModule()
-    {
-        $output = [];
-        $tsfe = $this->getTypoScriptFrontendController();
-        if ($this->getBackendUser()->uc['TSFE_adminConfig']['display_info']) {
-            // rows stored in tableArr consist of these columns:
-            // 0: label (already html-escaped!)
-            // 1: value (already html-escaped!)
-            // 2: bool (default: false) whether to show column 0 in <strong>-tags
-            $tableArr = [];
-            if ($this->extGetFeAdminValue('cache', 'noCache')) {
-                $theBytes = 0;
-                $count = 0;
-                if (!empty($tsfe->imagesOnPage)) {
-                    $tableArr[] = [$this->extGetLL('info_imagesOnPage'), count($tsfe->imagesOnPage), true];
-                    foreach ($GLOBALS['TSFE']->imagesOnPage as $file) {
-                        $fs = @filesize($file);
-                        $tableArr[] = [TAB . htmlspecialchars($file), GeneralUtility::formatSize($fs)];
-                        $theBytes += $fs;
-                        $count++;
-                    }
-                }
-                // Add an empty line
-                $tableArr[] = [$this->extGetLL('info_imagesSize'), GeneralUtility::formatSize($theBytes), true];
-                $tableArr[] = [$this->extGetLL('info_DocumentSize'), GeneralUtility::formatSize(strlen($tsfe->content)), true];
-                $tableArr[] = ['', ''];
-            }
-            $tableArr[] = [$this->extGetLL('info_id'), (int)$tsfe->id];
-            $tableArr[] = [$this->extGetLL('info_type'), (int)$tsfe->type];
-            $tableArr[] = [$this->extGetLL('info_groupList'), htmlspecialchars($tsfe->gr_list)];
-            $tableArr[] = [$this->extGetLL('info_noCache'), $this->extGetLL('info_noCache_' . ($tsfe->no_cache ? 'no' : 'yes'))];
-            $tableArr[] = [$this->extGetLL('info_countUserInt'), count($tsfe->config['INTincScript'] ?? [])];
-
-            if (!empty($tsfe->fe_user->user['uid'])) {
-                $tableArr[] = [$this->extGetLL('info_feuserName'), htmlspecialchars($tsfe->fe_user->user['username'])];
-                $tableArr[] = [$this->extGetLL('info_feuserId'), htmlspecialchars($tsfe->fe_user->user['uid'])];
-            }
-
-            $tableArr[] = [$this->extGetLL('info_totalParsetime'), htmlspecialchars($this->getTimeTracker()->getParseTime() . ' ms'), true];
-            $table = '';
-            foreach ($tableArr as $key => $arr) {
-                $label = !empty($arr[2]) ? '<strong>' . $arr[0] . '</strong>' : $arr[0];
-                $value = (string)$arr[1] !== '' ? $arr[1] : '';
-                // the "weird" construct here is intentional.
-                // reasoning: we ALWAYS encode when giving things to the view.
-                // But in this case $label and $value come in encoded, hence the double function call.
-                $table .= '
-                    <tr>
-                        <td>' . htmlspecialchars(htmlspecialchars_decode($label)) . '</td>
-                        <td>' . htmlspecialchars(htmlspecialchars_decode($value)) . '</td>
-                    </tr>';
-            }
+        $orderedModules = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies(
+            $modules
+        );
 
-            $output[] = '<div class="typo3-adminPanel-table-overflow">';
-            $output[] = '  <table class="typo3-adminPanel-table">';
-            $output[] = '    ' . $table;
-            $output[] = '  </table>';
-            $output[] = '</div>';
+        foreach ($orderedModules as $module) {
+            $this->modules[] = GeneralUtility::makeInstance($module['module']);
         }
-
-        return implode('', $output);
     }
 
     /*****************************************************
index e340a69..a789e1e 100644 (file)
@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Frontend\View;
 
 /**
  * Interface for classes which hook into AdminPanelView
+ *
+ * @deprecated since v9 will be removed in v10 - see AdminPanelModuleInterface
  */
 interface AdminPanelViewHookInterface
 {
index 4d910db..6daf31e 100644 (file)
@@ -13,11 +13,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\View;
  *
  * The TYPO3 project - inspiring people to share!
  */
-use Prophecy\Argument;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
-use TYPO3\CMS\Core\Imaging\Icon;
-use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -75,50 +72,4 @@ class AdminPanelViewTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $timestampReturned = $adminPanelMock->extGetFeAdminValue('preview', 'simulateDate');
         $this->assertEquals($timestamp, $timestampReturned);
     }
-
-    /////////////////////////////////////////////
-    // Test concerning extendAdminPanel hook
-    /////////////////////////////////////////////
-
-    /**
-     * @test
-     */
-    public function extendAdminPanelHookThrowsExceptionIfHookClassDoesNotImplementInterface()
-    {
-        $this->expectException(\UnexpectedValueException::class);
-        $this->expectExceptionCode(1311942539);
-        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel'][] = \TYPO3\CMS\Frontend\Tests\Unit\Fixtures\AdminPanelHookWithoutInterfaceFixture::class;
-        /** @var $adminPanelMock \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\View\AdminPanelView */
-        $adminPanelMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelView::class)
-            ->setMethods(['dummy'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $adminPanelMock->display();
-    }
-
-    /**
-     * @test
-     */
-    public function extendAdminPanelHookCallsExtendAdminPanelMethodOfHook()
-    {
-        $hookClass = $this->getUniqueId('tx_coretest');
-        $hookMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelViewHookInterface::class)
-            ->setMockClassName($hookClass)
-            ->getMock();
-        GeneralUtility::addInstance($hookClass, $hookMock);
-        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel'][] = $hookClass;
-        /** @var $adminPanelMock \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\View\AdminPanelView */
-        $adminPanelMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelView::class)
-            ->setMethods(['extGetLL'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $iconFactoryProphecy = $this->prophesize(IconFactory::class);
-        GeneralUtility::addInstance(IconFactory::class, $iconFactoryProphecy->reveal());
-        $iconProphecy = $this->prophesize(Icon::class);
-        $iconFactoryProphecy->getIcon(Argument::cetera())->willReturn($iconProphecy->reveal());
-        $iconProphecy->render(Argument::cetera())->willReturn('');
-        $adminPanelMock->initialize();
-        $hookMock->expects($this->once())->method('extendAdminPanel')->with($this->isType('string'), $this->isInstanceOf(\TYPO3\CMS\Frontend\View\AdminPanelView::class));
-        $adminPanelMock->display();
-    }
 }
diff --git a/typo3/sysext/frontend/Tests/UnitDeprecated/View/AdminPanelViewTest.php b/typo3/sysext/frontend/Tests/UnitDeprecated/View/AdminPanelViewTest.php
new file mode 100644 (file)
index 0000000..0f548c9
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+namespace TYPO3\CMS\Frontend\Tests\UnitDeprecated\View;
+
+/*
+ * 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 Prophecy\Argument;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * Test case
+ */
+class AdminPanelViewTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
+{
+    /**
+     * Subject is not notice free, disable E_NOTICES
+     */
+    protected static $suppressNotices = true;
+
+    /**
+     * Set up
+     */
+    protected function setUp()
+    {
+        $GLOBALS['LANG'] = $this->createMock(LanguageService::class);
+        $cacheManagerProphecy = $this->prophesize(CacheManager::class);
+        GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
+        $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
+        $cacheManagerProphecy->getCache('cache_pages')->willReturn($cacheFrontendProphecy->reveal());
+        $GLOBALS['TSFE'] = new TypoScriptFrontendController([], 1, 1);
+    }
+
+    protected function tearDown()
+    {
+        GeneralUtility::purgeInstances();
+        parent::tearDown();
+    }
+
+    /////////////////////////////////////////////
+    // Test concerning extendAdminPanel hook
+    /////////////////////////////////////////////
+
+    /**
+     * @test
+     */
+    public function extendAdminPanelHookThrowsExceptionIfHookClassDoesNotImplementInterface()
+    {
+        $this->expectException(\UnexpectedValueException::class);
+        $this->expectExceptionCode(1311942539);
+        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel'][] = \TYPO3\CMS\Frontend\Tests\Unit\Fixtures\AdminPanelHookWithoutInterfaceFixture::class;
+        /** @var $adminPanelMock \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\View\AdminPanelView */
+        $adminPanelMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelView::class)
+            ->setMethods(['dummy'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $adminPanelMock->display();
+    }
+
+    /**
+     * @test
+     */
+    public function extendAdminPanelHookCallsExtendAdminPanelMethodOfHook()
+    {
+        $hookClass = $this->getUniqueId('tx_coretest');
+        $hookMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelViewHookInterface::class)
+            ->setMockClassName($hookClass)
+            ->getMock();
+        GeneralUtility::addInstance($hookClass, $hookMock);
+        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_adminpanel.php']['extendAdminPanel'][] = $hookClass;
+        /** @var $adminPanelMock \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\View\AdminPanelView */
+        $adminPanelMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\View\AdminPanelView::class)
+            ->setMethods(['extGetLL'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $iconFactoryProphecy = $this->prophesize(IconFactory::class);
+        GeneralUtility::addInstance(IconFactory::class, $iconFactoryProphecy->reveal());
+        $iconProphecy = $this->prophesize(Icon::class);
+        $iconFactoryProphecy->getIcon(Argument::cetera())->willReturn($iconProphecy->reveal());
+        $iconProphecy->render(Argument::cetera())->willReturn('');
+        $adminPanelMock->initialize();
+        $hookMock->expects($this->once())->method('extendAdminPanel')->with($this->isType('string'), $this->isInstanceOf(\TYPO3\CMS\Frontend\View\AdminPanelView::class));
+        $adminPanelMock->display();
+    }
+}
index 62855f1..be799e4 100644 (file)
@@ -81,7 +81,7 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['move
 // Register hook to show preview info
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo']['cms'] = \TYPO3\CMS\Frontend\Hooks\FrontendHooks::class . '->hook_previewInfo';
 
-// Register for hookss to show preview of tt_content elements in page module
+// Register for hooks to show preview of tt_content elements in page module
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['image'] =
     \TYPO3\CMS\Frontend\Hooks\PageLayoutView\ImagePreviewRenderer::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['textpic'] =
@@ -114,3 +114,26 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers']['front
 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig(
     '<INCLUDE_TYPOSCRIPT: source="FILE:EXT:frontend/Configuration/TSconfig/Page/TCEFORM.txt">'
 );
+
+$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['frontend']['adminPanelModules'] = [
+    'preview' => [
+        'module' => \TYPO3\CMS\Frontend\AdminPanel\PreviewModule::class,
+        'before' => ['cache']
+    ],
+    'cache' => [
+        'module' => \TYPO3\CMS\Frontend\AdminPanel\CacheModule::class,
+        'after' => ['preview']
+    ],
+    'edit' => [
+        'module' => \TYPO3\CMS\Frontend\AdminPanel\EditModule::class,
+        'after' => ['cache']
+    ],
+    'tsdebug' => [
+        'module' => \TYPO3\CMS\Frontend\AdminPanel\TsDebugModule::class,
+        'after' => ['edit']
+    ],
+    'info' => [
+        'module' => \TYPO3\CMS\Frontend\AdminPanel\InfoModule::class,
+        'after' => ['tsdebug']
+    ]
+];
index a514943..a0cf3df 100644 (file)
@@ -149,4 +149,10 @@ return [
             'Deprecation-83740-CleanupOfAbstractRecordListBreaksHook.rst',
         ],
     ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_adminpanel.php\'][\'extendAdminPanel\']' => [
+        'restFiles' => [
+            'Deprecation-84045-AdminPanelHookDeprecated.rst',
+            'Feature-84045-NewAdminPanelModuleAPI.rst',
+        ],
+    ],
 ];