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[] = '
'; + $output[] = ' '; + $output[] = ' ' . $this->extGetLL('edit_openAB'); + $output[] = ' '; + $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', + ], + ], ];