From 61df57efe9284b2a9a26612f7ef2fa1e1019c10b Mon Sep 17 00:00:00 2001 From: Susanne Moog Date: Sun, 25 Feb 2018 12:21:31 +0100 Subject: [PATCH] [FEATURE] New API for the admin panel 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 Tested-by: TYPO3com Tested-by: Frank Naegler Reviewed-by: Benni Mack Tested-by: Benni Mack --- ...ecation-84045-AdminPanelHookDeprecated.rst | 32 ++ .../Feature-84045-NewAdminPanelModuleAPI.rst | 33 ++ .../Classes/AdminPanel/AbstractModule.php | 80 ++++ .../AdminPanel/AdminPanelModuleInterface.php | 50 +++ .../Classes/AdminPanel/CacheModule.php | 87 ++++ .../Classes/AdminPanel/EditModule.php | 136 ++++++ .../Classes/AdminPanel/InfoModule.php | 132 ++++++ .../Classes/AdminPanel/PreviewModule.php | 165 ++++++++ .../Classes/AdminPanel/TsDebugModule.php | 140 ++++++ .../frontend/Classes/View/AdminPanelView.php | 397 +++--------------- .../View/AdminPanelViewHookInterface.php | 2 + .../Tests/Unit/View/AdminPanelViewTest.php | 49 --- .../View/AdminPanelViewTest.php | 99 +++++ typo3/sysext/frontend/ext_localconf.php | 25 +- .../Php/ArrayDimensionMatcher.php | 6 + 15 files changed, 1044 insertions(+), 389 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-84045-AdminPanelHookDeprecated.rst create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-84045-NewAdminPanelModuleAPI.rst create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/AbstractModule.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/AdminPanelModuleInterface.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/CacheModule.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/EditModule.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/InfoModule.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/PreviewModule.php create mode 100644 typo3/sysext/frontend/Classes/AdminPanel/TsDebugModule.php create mode 100644 typo3/sysext/frontend/Tests/UnitDeprecated/View/AdminPanelViewTest.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 index 000000000000..7eee7eafe205 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84045-AdminPanelHookDeprecated.rst @@ -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 index 000000000000..bac629a0d83b --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-84045-NewAdminPanelModuleAPI.rst @@ -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 index 000000000000..43d90719b9b7 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/AbstractModule.php @@ -0,0 +1,80 @@ +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 index 000000000000..9e57b6774409 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/AdminPanelModuleInterface.php @@ -0,0 +1,50 @@ +getBackendUser()->uc['TSFE_adminConfig']['display_cache']) { + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + + $levels = $this->getBackendUser()->uc['TSFE_adminConfig']['cache_clearCacheLevels']; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = '
'; + } + 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 index 000000000000..343dffb61980 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/EditModule.php @@ -0,0 +1,136 @@ +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[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + } + + $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[] = ''; + } + } + 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 index 000000000000..3a7f435c9b25 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/InfoModule.php @@ -0,0 +1,132 @@ +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]) ? '' . $arr[0] . '' : $arr[0]); + $value = (string)$arr[1] !== '' ? $arr[1] : ''; + $table .= ' + + ' . $label . ' + ' . htmlspecialchars((string)$value) . ' + '; + } + + $output[] = '
'; + $output[] = ' '; + $output[] = ' ' . $table; + $output[] = '
'; + $output[] = '
'; + } + + 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 index 000000000000..15c54150d7f7 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/PreviewModule.php @@ -0,0 +1,165 @@ +getBackendUser()->uc['TSFE_adminConfig']['display_preview']) { + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + + // Simulate date + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + // the hidden field must be placed after the _hr field to avoid the timestamp being overridden by the date string + $output[] = ' '; + $output[] = '
'; + + // 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[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + } + } + 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 index 000000000000..a62258946963 --- /dev/null +++ b/typo3/sysext/frontend/Classes/AdminPanel/TsDebugModule.php @@ -0,0 +1,140 @@ +getBackendUser(); + if ($beuser->uc['TSFE_adminConfig']['display_tsdebug']) { + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + $output[] = ' '; + $output[] = ' '; + $output[] = '
'; + $output[] = '
'; + + $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); + } +} diff --git a/typo3/sysext/frontend/Classes/View/AdminPanelView.php b/typo3/sysext/frontend/Classes/View/AdminPanelView.php index 866735af57e7..dda6cc723d9e 100644 --- a/typo3/sysext/frontend/Classes/View/AdminPanelView.php +++ b/typo3/sysext/frontend/Classes/View/AdminPanelView.php @@ -17,15 +17,16 @@ 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. @@ -63,6 +64,13 @@ class AdminPanelView */ protected $extFeEditLoaded = false; + /** + * Array of adminPanel modules + * + * @var AdminPanelModuleInterface[] + */ + protected $modules = []; + /** * Constructor */ @@ -81,6 +89,7 @@ public function initialize() $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 @@ public function isAdminModuleOpen($key) /** * @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[] = '
'; $output[] = '
'; - $output[] = ' ' . $this->linkSectionHeader($key, $this->extGetLL($key)); + $output[] = ' ' . $this->linkSectionHeader($key, $label ?: $this->extGetLL($key)); $output[] = '
'; if ($this->isAdminModuleOpen($key)) { $output[] = '
'; @@ -283,13 +293,20 @@ public function display() $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 @@ protected function getHiddenFields($key, array $val) 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[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - - // Simulate date - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - // the hidden field must be placed after the _hr field to avoid the timestamp being overridden by the date string - $output[] = ' '; - $output[] = '
'; - $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[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - } - } - 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[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - - $levels = $this->getBackendUser()->uc['TSFE_adminConfig']['cache_clearCacheLevels']; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = '
'; - } - 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[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; + $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[] = ''; + 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[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - $output[] = ' '; - $output[] = ' '; - $output[] = '
'; - $output[] = '
'; - - $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 -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]) ? '' . $arr[0] . '' : $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 .= ' - - ' . htmlspecialchars(htmlspecialchars_decode($label)) . ' - ' . htmlspecialchars(htmlspecialchars_decode($value)) . ' - '; - } + $orderedModules = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies( + $modules + ); - $output[] = '
'; - $output[] = ' '; - $output[] = ' ' . $table; - $output[] = '
'; - $output[] = '
'; + foreach ($orderedModules as $module) { + $this->modules[] = GeneralUtility::makeInstance($module['module']); } - - return implode('', $output); } /***************************************************** diff --git a/typo3/sysext/frontend/Classes/View/AdminPanelViewHookInterface.php b/typo3/sysext/frontend/Classes/View/AdminPanelViewHookInterface.php index e340a69eacc3..a789e1e26932 100644 --- a/typo3/sysext/frontend/Classes/View/AdminPanelViewHookInterface.php +++ b/typo3/sysext/frontend/Classes/View/AdminPanelViewHookInterface.php @@ -16,6 +16,8 @@ /** * Interface for classes which hook into AdminPanelView + * + * @deprecated since v9 will be removed in v10 - see AdminPanelModuleInterface */ interface AdminPanelViewHookInterface { diff --git a/typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php b/typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php index 4d910dbb4693..6daf31e3b0fb 100644 --- a/typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php +++ b/typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php @@ -13,11 +13,8 @@ * * 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 @@ public function extGetFeAdminValueReturnsTimestamp() $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 index 000000000000..0f548c932915 --- /dev/null +++ b/typo3/sysext/frontend/Tests/UnitDeprecated/View/AdminPanelViewTest.php @@ -0,0 +1,99 @@ +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(); + } +} diff --git a/typo3/sysext/frontend/ext_localconf.php b/typo3/sysext/frontend/ext_localconf.php index 62855f12d69c..be799e4f84e2 100644 --- a/typo3/sysext/frontend/ext_localconf.php +++ b/typo3/sysext/frontend/ext_localconf.php @@ -81,7 +81,7 @@ // 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 @@ \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig( '' ); + +$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'] + ] +]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php index a514943823f8..a0cf3df631b2 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php @@ -149,4 +149,10 @@ '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', + ], + ], ];