diff --git a/Build/Sources/Sass/dashboard/_grid.scss b/Build/Sources/Sass/dashboard/_grid.scss index bb6eb4a0b425e6daf9aadd1bf3cb42110749c136..e79c19e7146e5091d60aa6a32d9c7b23fb362f62 100644 --- a/Build/Sources/Sass/dashboard/_grid.scss +++ b/Build/Sources/Sass/dashboard/_grid.scss @@ -72,19 +72,19 @@ user-select: auto !important; } -.dashboard-item--h4 { +.dashboard-item--h-medium { @media screen and (min-width: 750px) { height: $widget-height * 2; } } -.dashboard-item--h6 { +.dashboard-item--h-large { @media screen and (min-width: 750px) { height: $widget-height * 3; } } -.dashboard-item--w4 { +.dashboard-item--w-medium { width: 100%; @media screen and (min-width: 1285px) { @@ -92,6 +92,10 @@ } } +.dashboard-item--w-large { + width: 100%; +} + .dashboard-item-content { position: relative; width: 100%; diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst new file mode 100644 index 0000000000000000000000000000000000000000..7c32bc0f88682af6d36110075a62a0289c3c46a0 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst @@ -0,0 +1,138 @@ +.. include:: ../../Includes.txt + +============================================================ +Breaking: #90660 - Registration of dashboard widgets changed +============================================================ + +See :issue:`90660` + +Description +=========== + +As the registration of dashboard widgets changed to allow creation of widgets +through configuration, it is necessary to change your registration of widgets you +registered yourself in version 10.3. The abstracts used to kick start your +widgets are removed and the widgets shipped with EXT:dashboard are refactored. + +Impact +====== +As the abstracts previously used to kick-start a widget are removed, you need +to change to the new way of registering widgets. It will break the dashboard +module if you do not update your registration. + +Affected Installations +====================== + +All 3rd party extensions that registered an own widget with TYPO3 v10.3, will be +affected and need to update the widget registration. If you only used the widgets +shipped with core, you don't have to do anything yourself. + +Migration +========= + +Migration of widgets based on default widget types +-------------------------------------------------- + +This section is showing you how to migrate widgets that are based on one of +the existing widget types that are shipped by core. If your widgets are extending +one of the following classes, you can use this section to migrate your registration +to the new syntax. + +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractChartWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractCtaButtonWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractListWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractNumberWithIconWidget` +- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractRssWidget` + +First of all you need to update your registration in the :file:`Services.yaml` file. +An example of a registration of an RSS widget in the old version. + +**Before** + +.. code-block:: yaml + + Vendor\Package\Widgets\MyOwnRSSWidget: + arguments: [‘myOwnRSSWidget’] + tags: + - name: dashboard.widget + identifier: myOwnRSSWidget + widgetGroups: ‘general’ + +As you can now use the predefined widgets and only have to register your own +implementation with your own configuration, we have to alter this registration +a little bit. + +**Now** + +.. code-block:: yaml + + dashboard.widget.myOwnRSSWidget: + class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget' + arguments: + $view: '@dashboard.views.widget' + $cache: '@cache.dashboard.rss' + $options: + rssFile: 'https://typo3.org/rss' + # 12 hours cache + lifeTime: 43200 + tags: + - name: dashboard.widget + identifier: 'myOwnRSSWidget' + groupNames: ‘general’ + title: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.title' + description: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.description' + iconIdentifier: 'content-widget-rss' + height: 4 + width: 4 + +It starts with the name of the service. Best practise is to use a dot-styled +name as there will be now class with that name. On the second line, we define +which widget to use. In this case we choose the RssWidget from the dashboard +core extension. In the documentation, we explain all the arguments like :php:`$view` +and :php:`$cache`. For the migration you need the :php:`$options` argument. +As you can see we specify the RSS File and the cache lifetime for this feed. +In the old situation you have set these values in the class. +Now you can just put those values in the registration. + +The second part that changed a little bit, is that you need to set the title, +description, icon, height and width in the tags section of the registration. +You can still use translatable strings like +``LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.title``. +Important to remember is that the :yaml:`widgetGroups`` property changed to :yaml:`groupNames` +to stay consistent with other service registrations. + +In the following table you can see which WidgetType to use now based on the +abstract you used previously. + ++--------------------------------------+----------------------------------------------------------------------+ +| Previously used abstract | Widget class to use for your registration | ++======================================+======================================================================+ +| :php:`AbstractBarChartWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\BarChartWidget` | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractChartWidget` | This was only used as an abstract of the other chart widgets and is | +| | not used anymore. If you want another graph type, you have to create | +| | your own widget. | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractCtaButtonWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\CtaWidget` | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractDoughnutChartWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget` | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractListWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\ListWidget` | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractNumberWithIconWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget` | ++--------------------------------------+----------------------------------------------------------------------+ +| :php:`AbstractRssWidget` | :php:`TYPO3\CMS\Dashboard\Widgets\RssWidget` | ++--------------------------------------+----------------------------------------------------------------------+ + +You can check the documentation of EXT:dashboard to see the exact options for every type of widget. + +Migration of widgets based on own widget type +--------------------------------------------- +When you created your complete own widget type, the main thing to check is you +use the Dependency Injection options you have now. Please check the documentation +of EXT:dashboard to see how to create your own widget type and what options you +have. + +.. index:: Backend, ext:dashboard, NotScanned diff --git a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php index 47c683341d0f1b6775bc625f4d4efdcb2fa93f76..671b2278b109af253bfb862858099fe68333040f 100644 --- a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php +++ b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php @@ -27,15 +27,10 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Dashboard\Dashboard; -use TYPO3\CMS\Dashboard\DashboardPreset; +use TYPO3\CMS\Dashboard\DashboardInitializationService; use TYPO3\CMS\Dashboard\DashboardPresetRegistry; use TYPO3\CMS\Dashboard\DashboardRepository; -use TYPO3\CMS\Dashboard\WidgetGroupRegistry; -use TYPO3\CMS\Dashboard\WidgetRegistry; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalJavaScriptInterface; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Dashboard\WidgetGroupInitializationService; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; use TYPO3\CMS\Fluid\View\StandaloneView; @@ -56,36 +51,11 @@ class DashboardController extends AbstractController */ protected $view; - /** - * @var array - */ - protected $cssFiles = []; - - /** - * @var array - */ - protected $jsFiles = []; - - /** - * @var mixed[] - */ - protected $requireJsModules = []; - /** * @var Dashboard */ protected $currentDashboard; - /** - * @var Dashboard[] - */ - protected $dashboardsForCurrentUser; - - /** - * @var DashboardPreset[] - */ - protected $availableDashboardPresets; - /** * @var DashboardPresetRegistry */ @@ -97,76 +67,32 @@ class DashboardController extends AbstractController protected $dashboardRepository; /** - * @var WidgetGroupRegistry + * @var DashboardInitializationService */ - protected $widgetGroupRepository; + private $dashboardInitializationService; /** - * @var WidgetRegistry + * @var WidgetGroupInitializationService */ - protected $widgetRegistry; - - /** - * @var ConfigurationManagerInterface - */ - protected $configurationManager; + private $widgetGroupInitializationService; public function __construct( ModuleTemplate $moduleTemplate, UriBuilder $uriBuilder, DashboardPresetRegistry $dashboardPresetRepository, DashboardRepository $dashboardRepository, - WidgetGroupRegistry $widgetGroupRepository, - WidgetRegistry $widgetRegistry + DashboardInitializationService $dashboardInitializationService, + WidgetGroupInitializationService $widgetGroupInitializationService ) { $this->moduleTemplate = $moduleTemplate; $this->uriBuilder = $uriBuilder; $this->dashboardPresetRepository = $dashboardPresetRepository; $this->dashboardRepository = $dashboardRepository; - $this->widgetGroupRepository = $widgetGroupRepository; - $this->widgetRegistry = $widgetRegistry; + $this->dashboardInitializationService = $dashboardInitializationService; - $this->initializeDashboardsForCurrentUser(); - } - - protected function initializeDashboardsForCurrentUser(): void - { - $this->dashboardsForCurrentUser = $this->getDashboardsForCurrentUser(); - $this->currentDashboard = $this->dashboardRepository->getDashboardByIdentifier($this->loadCurrentDashboard()); - - $this->availableDashboardPresets = $this->dashboardPresetRepository->getDashboardPresets(); - - if (empty($this->dashboardsForCurrentUser)) { - $this->dashboardsForCurrentUser = []; - - $userConfig = $this->getBackendUser()->getTSConfig(); - $dashboardsToCreate = GeneralUtility::trimExplode( - ',', - $userConfig['options.']['dashboard.']['dashboardPresetsForNewUsers'] ?? 'default' - ); - - /** @var DashboardPreset $dashboardPreset */ - foreach ($this->availableDashboardPresets as $dashboardPreset) { - if (in_array($dashboardPreset->getIdentifier(), $dashboardsToCreate, true)) { - $dashboard = $this->dashboardRepository->create( - $dashboardPreset, - (int)$this->getBackendUser()->user['uid'] - ); - - if ($dashboard instanceof Dashboard) { - $this->dashboardsForCurrentUser[$dashboard->getIdentifier()] = $dashboard; - } - } - } - } - - if (!$this->currentDashboard instanceof Dashboard) { - $this->currentDashboard = reset($this->dashboardsForCurrentUser); - $this->saveCurrentDashboard($this->currentDashboard->getIdentifier()); - } - - $this->currentDashboard->initializeWidgets(); - $this->defineResourcesOfWidgets($this->currentDashboard->getWidgets()); + $this->dashboardInitializationService->initializeDashboards($this->getBackendUser()); + $this->currentDashboard = $this->dashboardInitializationService->getCurrentDashboard(); + $this->widgetGroupInitializationService = $widgetGroupInitializationService; } /** @@ -178,9 +104,9 @@ class DashboardController extends AbstractController public function mainAction(): void { $this->view->assignMultiple([ - 'availableDashboards' => $this->dashboardsForCurrentUser, - 'dashboardPresets' => $this->availableDashboardPresets, - 'widgetGroups' => $this->buildWidgetGroupsConfiguration(), + 'availableDashboards' => $this->dashboardInitializationService->getDashboardsForUser(), + 'dashboardPresets' => $this->dashboardPresetRepository->getDashboardPresets(), + 'widgetGroups' => $this->widgetGroupInitializationService->buildWidgetGroupsConfiguration(), 'currentDashboard' => $this->currentDashboard, 'addWidgetUri' => (string)$this->uriBuilder->buildUriFromRoute('dashboard', ['action' => 'addWidget']), 'addDashboardUri' => (string)$this->uriBuilder->buildUriFromRoute('dashboard', ['action' => 'addDashboard']), @@ -333,17 +259,17 @@ class DashboardController extends AbstractController */ protected function addFrontendResources(PageRenderer $pageRenderer): void { - foreach ($this->requireJsModules as $requireJsModule) { + foreach ($this->dashboardInitializationService->getRequireJsModules() as $requireJsModule) { if (is_array($requireJsModule)) { $pageRenderer->loadRequireJsModule($requireJsModule[0], $requireJsModule[1]); } else { $pageRenderer->loadRequireJsModule($requireJsModule); } } - foreach ($this->cssFiles as $cssFile) { + foreach ($this->dashboardInitializationService->getCssFiles() as $cssFile) { $pageRenderer->addCssFile($cssFile); } - foreach ($this->jsFiles as $jsFile) { + foreach ($this->dashboardInitializationService->getJsFiles() as $jsFile) { $pageRenderer->addJsFile($jsFile); } } @@ -377,111 +303,4 @@ class DashboardController extends AbstractController $pageRenderer->loadRequireJsModule('TYPO3/CMS/Dashboard/DashboardDelete'); $pageRenderer->addCssFile($publicResourcesPath . 'Css/dashboard.css'); } - - /** - * @return Dashboard[] - */ - protected function getDashboardsForCurrentUser(): array - { - $dashboards = []; - foreach ($this->dashboardRepository->getDashboardsForUser((int)$this->getBackendUser()->user['uid']) as $dashboard) { - $dashboards[$dashboard->getIdentifier()] = $dashboard; - } - return $dashboards; - } - - /** - * Define the different groups of widgets as shown in the modal when adding a widget to the current dashboard - * - * @return array - */ - protected function buildWidgetGroupsConfiguration(): array - { - $groupConfigurations = []; - foreach ($this->widgetGroupRepository->getWidgetGroups() as $widgetGroup) { - $widgetInstances = []; - $widgetGroupIdentifier = $widgetGroup->getIdentifier(); - - $widgetsForGroup = $this->widgetRegistry->getAvailableWidgetsForWidgetGroup($widgetGroupIdentifier); - foreach ($widgetsForGroup as $identifier => $widgetService) { - $widgetInstances[$identifier] = GeneralUtility::makeInstance($widgetService); - } - - $groupConfigurations[$widgetGroupIdentifier] = [ - 'identifier' => $widgetGroupIdentifier, - 'title' => $widgetGroup->getTitle(), - 'widgets' => $widgetInstances - ]; - } - - return $groupConfigurations; - } - - /** - * @param array $widgets - */ - protected function defineResourcesOfWidgets(array $widgets): void - { - foreach ($widgets as $widget) { - if ($widget instanceof RequireJsModuleInterface) { - $this->defineRequireJsModules($widget); - } - if ($widget instanceof AdditionalCssInterface) { - $this->defineCssFiles($widget); - } - if ($widget instanceof AdditionalJavaScriptInterface) { - $this->defineJsFiles($widget); - } - } - } - - /** - * Add the RequireJS modules needed by some widgets - * - * @param RequireJsModuleInterface $widgetInstance - */ - protected function defineRequireJsModules(RequireJsModuleInterface $widgetInstance): void - { - foreach ($widgetInstance->getRequireJsModules() as $moduleNameOrIndex => $callbackOrModuleName) { - if (is_string($moduleNameOrIndex)) { - $this->requireJsModules[] = [$moduleNameOrIndex, $callbackOrModuleName]; - } else { - $this->requireJsModules[] = $callbackOrModuleName; - } - } - } - - /** - * Define the correct path of the JS files of a widget and add them to the list of JS files that needs to be - * included - * - * @param AdditionalJavaScriptInterface $widgetInstance - */ - protected function defineJsFiles(AdditionalJavaScriptInterface $widgetInstance): void - { - foreach ($widgetInstance->getJsFiles() as $key => $jsFile) { - if (strpos($jsFile, 'EXT:') === 0) { - $jsFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFile)); - } - $this->jsFiles[$jsFile] = $jsFile; - } - } - - /** - * Define the correct path of the CSS files of a widget and add them to the list of CSS files that needs to be - * included - * - * @param AdditionalCssInterface $widgetInstance - */ - protected function defineCssFiles(AdditionalCssInterface $widgetInstance): void - { - foreach ($widgetInstance->getCssFiles() as $cssFile) { - if (strpos($cssFile, 'EXT:') === 0) { - $cssFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFile)); - } - if (!in_array($cssFile, $this->cssFiles, true)) { - $this->cssFiles[$cssFile] = $cssFile; - } - } - } } diff --git a/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php b/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php index 1f3bc7b825a0b2140086794c27d80a022fdfcf80..dbe46bd9527a7312c44731d978c5f107d664e270 100644 --- a/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php +++ b/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php @@ -18,7 +18,6 @@ namespace TYPO3\CMS\Dashboard\Controller; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Http\JsonResponse; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Dashboard\Dashboard; use TYPO3\CMS\Dashboard\DashboardRepository; use TYPO3\CMS\Dashboard\WidgetRegistry; @@ -58,23 +57,21 @@ class WidgetAjaxController extends AbstractController public function getContent(ServerRequestInterface $request): ResponseInterface { $queryParams = $request->getQueryParams(); - $availableWidgets = $this->widgetRegistry->getAvailableWidgets(); - if (empty((string)$queryParams['widget']) || !array_key_exists((string)$queryParams['widget'], $availableWidgets)) { + try { + $widgetObject = $this->widgetRegistry->getAvailableWidget((string)$queryParams['widget']); + } catch (\InvalidArgumentException $e) { return new JsonResponse(['error' => 'Widget is not available!']); } - $widgetObject = GeneralUtility::makeInstance($availableWidgets[(string)$queryParams['widget']]); - - $eventData = $widgetObject instanceof EventDataInterface ? $widgetObject->getEventData() : []; if (!$widgetObject instanceof WidgetInterface) { - return new JsonResponse(['error' => 'Widget doesnt have a valid widget class']); + return new JsonResponse(['error' => 'Widget doesn\'t have a valid widget class']); } $data = [ 'widget' => $queryParams['widget'], 'content' => $widgetObject->renderWidgetContent(), - 'eventdata' => $eventData, + 'eventdata' => $widgetObject instanceof EventDataInterface ? $widgetObject->getEventData() : [], ]; return new JsonResponse($data); diff --git a/typo3/sysext/dashboard/Classes/Dashboard.php b/typo3/sysext/dashboard/Classes/Dashboard.php index e9ea58199151fa48a2e750930f4bd0cc4bf6a68f..df66eeaf7cffd44b6330bbad565ad32269aa5e36 100644 --- a/typo3/sysext/dashboard/Classes/Dashboard.php +++ b/typo3/sysext/dashboard/Classes/Dashboard.php @@ -17,6 +17,7 @@ namespace TYPO3\CMS\Dashboard; use Psr\Container\ContainerInterface; use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; class Dashboard @@ -105,11 +106,9 @@ class Dashboard { $availableWidgets = $this->widgetRegistry->getAvailableWidgets(); foreach ($this->widgetConfig as $hash => $widgetConfig) { + /* @var WidgetConfigurationInterface $widgetConfig */ if (array_key_exists($widgetConfig['identifier'], $availableWidgets)) { - $widgetObject = $this->container->get($availableWidgets[$widgetConfig['identifier']]); - if ($widgetObject instanceof WidgetInterface) { - $this->widgets[$hash] = $widgetObject; - } + $this->widgets[$hash] = $availableWidgets[$widgetConfig['identifier']]; } } } diff --git a/typo3/sysext/dashboard/Classes/DashboardInitializationService.php b/typo3/sysext/dashboard/Classes/DashboardInitializationService.php new file mode 100644 index 0000000000000000000000000000000000000000..088e2c4177f88cc26435a14c294d38441c8ddcb5 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/DashboardInitializationService.php @@ -0,0 +1,235 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Dashboard; + +/* + * 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\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\PathUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalJavaScriptInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface; + +class DashboardInitializationService +{ + protected const MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER = 'dashboard/current_dashboard/'; + + /** + * @var DashboardRepository + */ + private $dashboardRepository; + + /** + * @var DashboardPresetRegistry + */ + private $dashboardPresetRegistry; + + /** + * @var Dashboard + */ + private $currentDashboard; + + /** + * @var BackendUserAuthentication + */ + private $user; + + /** + * @var array + */ + private $requireJsModules = []; + private $jsFiles = []; + private $cssFiles = []; + + public function __construct( + DashboardRepository $dashboardRepository, + DashboardPresetRegistry $dashboardPresetRegistry + ) { + $this->dashboardRepository = $dashboardRepository; + $this->dashboardPresetRegistry = $dashboardPresetRegistry; + } + + public function initializeDashboards(BackendUserAuthentication $user): void + { + $this->user = $user; + $this->currentDashboard = $this->defineCurrentDashboard(); + + $this->currentDashboard->initializeWidgets(); + $this->defineResourcesOfWidgets($this->currentDashboard->getWidgets()); + } + + public function getCurrentDashboard(): Dashboard + { + return $this->currentDashboard; + } + + protected function defineCurrentDashboard(): Dashboard + { + $currentDashboard = $this->dashboardRepository->getDashboardByIdentifier($this->loadCurrentDashboard($this->user)); + if (!$currentDashboard instanceof Dashboard) { + $dashboards = $this->getDashboardsForUser(); + $currentDashboard = reset($dashboards); + $this->saveCurrentDashboard($this->user, $currentDashboard->getIdentifier()); + } + + return $currentDashboard; + } + + protected function createDefaultDashboards(): array + { + $dashboardsForUser = []; + + $userConfig = $this->user->getTSConfig(); + $dashboardsToCreate = GeneralUtility::trimExplode( + ',', + $userConfig['options.']['dashboard.']['dashboardPresetsForNewUsers'] ?? 'default' + ); + + /** @var DashboardPreset $dashboardPreset */ + foreach ($this->dashboardPresetRegistry->getDashboardPresets() as $dashboardPreset) { + if (in_array($dashboardPreset->getIdentifier(), $dashboardsToCreate, true)) { + $dashboard = $this->dashboardRepository->create( + $dashboardPreset, + (int)$this->user->user['uid'] + ); + + if ($dashboard instanceof Dashboard) { + $dashboardsForUser[$dashboard->getIdentifier()] = $dashboard; + } + } + } + + return $dashboardsForUser; + } + + /** + * @return Dashboard[] + */ + public function getDashboardsForUser(): array + { + $dashboards = []; + foreach ($this->dashboardRepository->getDashboardsForUser((int)$this->user->user['uid']) as $dashboard) { + $dashboards[$dashboard->getIdentifier()] = $dashboard; + } + + if ($dashboards === []) { + $dashboards = $this->createDefaultDashboards(); + } + + return $dashboards; + } + + /** + * @param array $widgets + */ + protected function defineResourcesOfWidgets(array $widgets): void + { + foreach ($widgets as $widget) { + $concreteInstance = GeneralUtility::makeInstance($widget->getServiceName()); + if ($concreteInstance instanceof RequireJsModuleInterface) { + $this->defineRequireJsModules($concreteInstance); + } + if ($concreteInstance instanceof AdditionalCssInterface) { + $this->defineCssFiles($concreteInstance); + } + if ($concreteInstance instanceof AdditionalJavaScriptInterface) { + $this->defineJsFiles($concreteInstance); + } + } + } + + /** + * Add the RequireJS modules needed by some widgets + * + * @param RequireJsModuleInterface $widgetInstance + */ + protected function defineRequireJsModules(RequireJsModuleInterface $widgetInstance): void + { + foreach ($widgetInstance->getRequireJsModules() as $moduleNameOrIndex => $callbackOrModuleName) { + if (is_string($moduleNameOrIndex)) { + $this->requireJsModules[] = [$moduleNameOrIndex, $callbackOrModuleName]; + } else { + $this->requireJsModules[] = $callbackOrModuleName; + } + } + } + + /** + * Define the correct path of the JS files of a widget and add them to the list of JS files that needs to be + * included + * + * @param AdditionalJavaScriptInterface $widgetInstance + */ + protected function defineJsFiles(AdditionalJavaScriptInterface $widgetInstance): void + { + foreach ($widgetInstance->getJsFiles() as $jsFile) { + if (strpos($jsFile, 'EXT:') === 0) { + $jsFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFile)); + } + $this->jsFiles[$jsFile] = $jsFile; + } + } + + /** + * Define the correct path of the CSS files of a widget and add them to the list of CSS files that needs to be + * included + * + * @param AdditionalCssInterface $widgetInstance + */ + protected function defineCssFiles(AdditionalCssInterface $widgetInstance): void + { + foreach ($widgetInstance->getCssFiles() as $cssFile) { + if (strpos($cssFile, 'EXT:') === 0) { + $cssFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFile)); + } + $this->cssFiles[$cssFile] = $cssFile; + } + } + + protected function loadCurrentDashboard(BackendUserAuthentication $user): string + { + return $user->getModuleData(self::MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER) ?? ''; + } + + protected function saveCurrentDashboard(BackendUserAuthentication $user, string $identifier): void + { + $user->pushModuleData(self::MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER, $identifier); + } + + /** + * @return array + */ + public function getRequireJsModules(): array + { + return $this->requireJsModules; + } + + /** + * @return array + */ + public function getJsFiles(): array + { + return $this->jsFiles; + } + + /** + * @return array + */ + public function getCssFiles(): array + { + return $this->cssFiles; + } +} diff --git a/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php b/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php index 47f0b475d7dbb4627d8f9776bfbcc882c9896447..f22edef4d98094d1a5563c2da104ff12813b1ddc 100644 --- a/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php +++ b/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php @@ -17,8 +17,11 @@ namespace TYPO3\CMS\Dashboard\DependencyInjection; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Dashboard\WidgetRegistry; +use TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration; /** * @internal @@ -44,6 +47,7 @@ final class DashboardWidgetPass implements CompilerPassInterface public function process(ContainerBuilder $container): void { $widgetRegistryDefinition = $container->findDefinition(WidgetRegistry::class); + /* @var Definition|bool $widgetRegistryDefinition */ if (!$widgetRegistryDefinition) { return; } @@ -51,19 +55,72 @@ final class DashboardWidgetPass implements CompilerPassInterface foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) { $definition = $container->findDefinition($serviceName); $definition->setPublic(true); - // Widgets are handled like prototypes right now (will require state) - // @todo: Widgets should preferably be services, but that will require WidgetInterface - // to change - $definition->setShared(false); - $className = $definition->getClass() ?? $serviceName; + foreach ($tags as $attributes) { $identifier = $attributes['identifier'] ?? $serviceName; - $widgetRegistryDefinition->addMethodCall('registerWidget', [ + $attributes['identifier'] = $identifier; + $attributes['serviceName'] = $serviceName; + $attributes = $this->convertAttributes($attributes); + + $configurationServiceName = $this->registerWidgetConfigurationService( + $container, $identifier, - $serviceName, - GeneralUtility::trimExplode(',', $attributes['widgetGroups'] ?? '', true) - ]); + $attributes + ); + $definition->setArgument('$configuration', new Reference($configurationServiceName)); + + $widgetRegistryDefinition->addMethodCall('registerWidget', [$identifier . 'WidgetConfiguration']); } } } + + private function convertAttributes(array $attributes): array + { + $attributes = array_merge([ + 'iconIdentifier' => 'content-dashboard', + 'height' => 'small', + 'width' => 'small', + ], $attributes); + + if (isset($attributes['groupNames'])) { + $attributes['groupNames'] = GeneralUtility::trimExplode(',', $attributes['groupNames'], true); + } else { + $attributes['groupNames'] = []; + } + + if (isset($attributes['additionalCssClasses'])) { + $attributes['additionalCssClasses'] = GeneralUtility::trimExplode(' ', $attributes['additionalCssClasses'], true); + } else { + $attributes['additionalCssClasses'] = []; + } + + return $attributes; + } + + private function registerWidgetConfigurationService( + ContainerBuilder $container, + string $widgetIdentifier, + array $arguments + ): string { + $serviceName = $widgetIdentifier . 'WidgetConfiguration'; + + $definition = new Definition( + WidgetConfiguration::class, + $this->adjustArgumentsForDi($arguments) + ); + $definition->setPublic(true); + $container->addDefinitions([$serviceName => $definition]); + + return $serviceName; + } + + private function adjustArgumentsForDi(array $arguments): array + { + foreach ($arguments as $key => $value) { + $arguments['$' . $key] = $value; + unset($arguments[$key]); + } + + return $arguments; + } } diff --git a/typo3/sysext/dashboard/Classes/ServiceProvider.php b/typo3/sysext/dashboard/Classes/ServiceProvider.php index 52f06253759e426fee6e0efeaf5693d22981d104..51784438545d7c9601787a8b91463eec5801d509 100644 --- a/typo3/sysext/dashboard/Classes/ServiceProvider.php +++ b/typo3/sysext/dashboard/Classes/ServiceProvider.php @@ -131,32 +131,6 @@ class ServiceProvider extends AbstractServiceProvider return $widgetGroupRegistry; } - public static function configureWidgetRegistry( - ContainerInterface $container, - WidgetRegistry $widgetRegistry = null - ): WidgetRegistry { - $widgetRegistry = $widgetRegistry ?? self::new($container, WidgetRegistry::class); - $cache = $container->get('cache.core'); - - $cacheIdentifier = 'Dashboard_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'Widgets'); - if ($cache->has($cacheIdentifier)) { - $widgetsFromPackages = $cache->require($cacheIdentifier); - } else { - $widgetsFromPackages = $container->get('dashboard.widgets')->getArrayCopy(); - $cache->set($cacheIdentifier, 'return ' . var_export($widgetsFromPackages, true) . ';'); - } - - foreach ($widgetsFromPackages as $identifier => $options) { - $widgetRegistry->registerWidget( - $identifier, - $options['class'], - (array)$options['widgetGroups'] - ); - } - - return $widgetRegistry; - } - /** * @param ContainerInterface $container * @param ArrayObject $presets diff --git a/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php b/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php new file mode 100644 index 0000000000000000000000000000000000000000..890cc98ade0f2b81a0427a3e8b7db823676934ab --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php @@ -0,0 +1,34 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Utility; + +/* + * 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\Dashboard\Widgets\Interfaces\ButtonProviderInterface; + +class ButtonUtility +{ + public static function generateButtonConfig(?ButtonProviderInterface $buttonProvider): array + { + if ($buttonProvider instanceof ButtonProviderInterface && $buttonProvider->getTitle() && $buttonProvider->getLink()) { + return [ + 'text' => $buttonProvider->getTitle(), + 'link' => $buttonProvider->getLink(), + 'target' => $buttonProvider->getTarget(), + ]; + } + + return []; + } +} diff --git a/typo3/sysext/dashboard/Classes/Views/Factory.php b/typo3/sysext/dashboard/Classes/Views/Factory.php new file mode 100644 index 0000000000000000000000000000000000000000..bcc072325696b8fb21741a76518083815417405b --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Views/Factory.php @@ -0,0 +1,30 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Views; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Fluid\View\StandaloneView; + +class Factory +{ + public static function widgetTemplate(): StandaloneView + { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('dashboard'); + + return $view; + } +} diff --git a/typo3/sysext/dashboard/Classes/WidgetApi.php b/typo3/sysext/dashboard/Classes/WidgetApi.php new file mode 100644 index 0000000000000000000000000000000000000000..2fce884df3658b426cf7e582933d1245c2682ecf --- /dev/null +++ b/typo3/sysext/dashboard/Classes/WidgetApi.php @@ -0,0 +1,38 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard; + +/* + * 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! + */ + +/** + * Provides API for widgets. + */ +class WidgetApi +{ + /** + * Provides default colors to use for charts. + * + * @return array Hex codes of default colors. + */ + public static function getDefaultChartColors(): array + { + return [ + '#ff8700', + '#a4276a', + '#1a568f', + '#4c7e3a', + '#69bbb5', + ]; + } +} diff --git a/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php b/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php new file mode 100644 index 0000000000000000000000000000000000000000..8fec06754d78e11e16466435b884827c9380af74 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php @@ -0,0 +1,73 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard; + +/* + * 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\Localization\LanguageService; + +class WidgetGroupInitializationService +{ + /** + * @var WidgetGroupRegistry + */ + private $widgetGroupRegistry; + + /** + * @var WidgetRegistry + */ + private $widgetRegistry; + + public function __construct(WidgetGroupRegistry $widgetGroupRegistry, WidgetRegistry $widgetRegistry) + { + $this->widgetGroupRegistry = $widgetGroupRegistry; + $this->widgetRegistry = $widgetRegistry; + } + + /** + * Define the different groups of widgets as shown in the modal when adding a widget to the current dashboard + * + * @return array + */ + public function buildWidgetGroupsConfiguration(): array + { + $groupConfigurations = []; + foreach ($this->widgetGroupRegistry->getWidgetGroups() as $widgetGroup) { + $widgets = []; + $widgetGroupIdentifier = $widgetGroup->getIdentifier(); + + $widgetsForGroup = $this->widgetRegistry->getAvailableWidgetsForWidgetGroup($widgetGroupIdentifier); + foreach ($widgetsForGroup as $widgetConfiguration) { + $widgets[$widgetConfiguration->getIdentifier()] = [ + 'iconIdentifier' => $widgetConfiguration->getIconIdentifier(), + 'title' => $this->getLanguageService()->sl($widgetConfiguration->getTitle()), + 'description' => $this->getLanguageService()->sl($widgetConfiguration->getDescription()), + ]; + } + + $groupConfigurations[$widgetGroupIdentifier] = [ + 'identifier' => $widgetGroupIdentifier, + 'title' => $widgetGroup->getTitle(), + 'widgets' => $widgets, + ]; + } + + return $groupConfigurations; + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/typo3/sysext/dashboard/Classes/WidgetRegistry.php b/typo3/sysext/dashboard/Classes/WidgetRegistry.php index f5cd73138292acd75db4197b87e45253e577d589..97172c257a2e3a3177d38fb5cec60142a901d98e 100644 --- a/typo3/sysext/dashboard/Classes/WidgetRegistry.php +++ b/typo3/sysext/dashboard/Classes/WidgetRegistry.php @@ -18,6 +18,7 @@ namespace TYPO3\CMS\Dashboard; use Psr\Container\ContainerInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; class WidgetRegistry implements SingletonInterface @@ -52,6 +53,18 @@ class WidgetRegistry implements SingletonInterface return $this->widgets; } + /** + * @throws \InvalidArgumentException If requested identifier does not exist. + */ + public function getAvailableWidget(string $identifier): WidgetInterface + { + if (array_key_exists($identifier, $this->getAvailableWidgets())) { + return $this->container->get($this->widgets[$identifier]->getServiceName()); + } + + throw new \InvalidArgumentException('Requested widget "' . $identifier . '" does not exist.', 1584777201); + } + public function getAvailableWidgetsForWidgetGroup(string $widgetGroupIdentifier): array { if (!array_key_exists($widgetGroupIdentifier, $this->widgetsPerWidgetGroup)) { @@ -60,36 +73,40 @@ class WidgetRegistry implements SingletonInterface return $this->checkPermissionOfWidgets($this->widgetsPerWidgetGroup[$widgetGroupIdentifier]); } - public function registerWidget(string $identifier, string $widgetServiceName, array $widgetGroupIdentifiers): void + public function registerWidget(string $serviceName): void { - $this->widgets[$identifier] = $widgetServiceName; - foreach ($widgetGroupIdentifiers as $widgetGroupIdentifier) { - $this->widgetsPerWidgetGroup[$widgetGroupIdentifier][$identifier] = $widgetServiceName; + $widgetConfiguration = $this->container->get($serviceName); + $this->widgets[$widgetConfiguration->getIdentifier()] = $widgetConfiguration; + foreach ($widgetConfiguration->getGroupNames() as $groupIdentifier) { + $this->widgetsPerWidgetGroup = ArrayUtility::setValueByPath( + $this->widgetsPerWidgetGroup, + $groupIdentifier . '/' . $widgetConfiguration->getIdentifier(), + $widgetConfiguration + ); } } protected function checkPermissionOfWidgets(array $widgets): array { - return array_filter($widgets, function ($widgetIdentifier) { - return $this->getBackendUser()->check('available_widgets', $widgetIdentifier); + return array_filter($widgets, function ($identifier) { + return $this->getBackendUser()->check('available_widgets', $identifier); }, ARRAY_FILTER_USE_KEY); } - protected function getBackendUser(): BackendUserAuthentication + public function widgetItemsProcFunc(array &$parameters): void { - return $GLOBALS['BE_USER']; - } - - public function widgetItemsProcFunc(array $parameters): void - { - foreach ($this->widgets as $identifier => $widget) { - $widgetObject = $this->container->get($widget); + foreach ($this->widgets as $widget) { $parameters['items'][] = [ - $widgetObject->getTitle() , - $identifier, - $widgetObject->getIconIdentifier() ?? 'content-dashboard', - $widgetObject->getDescription() + $widget->getTitle(), + $widget->getIdentifier(), + $widget->getIconIdentifier(), + $widget->getDescription(), ]; } } + + protected function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } } diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php deleted file mode 100644 index 875dd583f1a37bccd9dcaa10520f185ada24a569..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * The AbstractBarChartWidget class is the basic widget class for bar charts. - * It is possible to extend this class for custom widgets. - * In your class you have to store the data to display in $this->chartData. - * More information can be found in the documentation. - */ -abstract class AbstractBarChartWidget extends AbstractChartWidget -{ - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-chart-bar'; - - /** - * @var string - */ - protected $chartType = 'bar'; - - /** - * @var mixed[] - */ - protected $chartOptions = [ - 'maintainAspectRatio' => false, - 'legend' => [ - 'display' => false - ], - 'scales' => [ - 'yAxes' => [ - [ - 'ticks' => [ - 'beginAtZero' => true - ] - ] - ], - 'xAxes' => [ - [ - 'ticks' => [ - 'maxTicksLimit' => 15 - ] - ] - ] - ] - ]; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php deleted file mode 100644 index 7e93c6bd067f9b3860c4366eabf2af8825ef31b5..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php +++ /dev/null @@ -1,174 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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\Dashboard\Widgets\Interfaces\AdditionalCssInterface; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface; - -/** - * The AbstractChartWidget class is the basic widget class for all chart widgets. - * It is possible to extend this class for custom widgets. EXT:dashboard also provides - * more special chart types for widgets (bar chart and doughnut chart). - */ -abstract class AbstractChartWidget extends AbstractWidget implements AdditionalCssInterface, RequireJsModuleInterface, EventDataInterface -{ - /** - * The type of chart you want to show. The types of charts that are available can be found in the - * chart.js documentation. - * - * @link https://www.chartjs.org/docs/latest/charts/ - * - * Currently the following chart types are implemented: - * @see AbstractBarChartWidget - * @see AbstractDoughnutChartWidget - * - * @var string - */ - protected $chartType = ''; - - /** - * This property should contain the data for the graph. The data and options you have depend on the type - * of chart. More information can be found in the documentation of the specific type. - * - * @link https://www.chartjs.org/docs/latest/charts/bar.html#data-structure - * @link https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure - * - * @var array - */ - protected $chartData = []; - - /** - * This property should contain the options to configure your graph. The options available are based on the type - * of chart. The implementations of the charts will contain the default options for that type of graph. - * - * @see AbstractBarChartWidget::$chartOptions - * @see AbstractDoughnutChartWidget::$chartOptions - * - * @var array - */ - protected $chartOptions = []; - - /** - * This property can be used to pass data to the JavaScript that will handle the content rendering. For - * charts, the only property necessary for charts is the graphConfig element. This will be set in the - * getEventData method of this class. Setting this property manually is therefore not needed. - * - * @see getEventData - * - * @var array - */ - protected $eventData = []; - - /** - * The default colors that will be used for the graphs. - * - * @var string[] - */ - protected $chartColors = ['#ff8700', '#a4276a', '#1a568f', '#4c7e3a', '#69bbb5']; - - /** - * If you want to show a button below the graph, you need to set the title and the link to the button. - * This text can be a fixed string or can contain a translatable string. - * - * @var string - */ - protected $buttonText = ''; - - /** - * The link of the button. Besides the link, also the buttonText should be set before the buttons will be - * rendered. - * - * @var string - */ - protected $buttonLink = ''; - - /** - * By default the link of the button will be opened in the current frame. By setting the target, you can specify - * where the link should be opened. - * - * @var string - */ - protected $buttonTarget = ''; - - /** - * This CSS class is used so the JavaScript can identify the widgets containing graphs. Overriding this - * property could lead to the widget not working. - * - * @internal - * - * @var string - */ - protected $additionalClasses = 'dashboard-item--chart'; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-chart'; - - /** - * @var string - */ - protected $templateName = 'ChartWidget'; - - /** - * This method is used to define the data that will be shown in the graph. This method should be implemented - * and should set the data in the chartData property. - * - * @see chartData - */ - abstract protected function prepareChartData(): void; - - protected function initializeView(): void - { - parent::initializeView(); - - if ($this->buttonLink && $this->buttonText) { - $this->view->assign( - 'button', - [ - 'text' => $this->getLanguageService()->sL($this->buttonText) ?: $this->buttonText, - 'link' => $this->buttonLink, - 'target' => $this->buttonTarget - ] - ); - } - } - - public function getEventData(): array - { - $this->prepareChartData(); - $this->eventData['graphConfig'] = [ - 'type' => $this->chartType, - 'data' => $this->chartData, - 'options' => $this->chartOptions - ]; - return $this->eventData; - } - - public function getCssFiles(): array - { - return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css']; - } - - public function getRequireJsModules(): array - { - return [ - 'TYPO3/CMS/Dashboard/Contrib/chartjs', - 'TYPO3/CMS/Dashboard/ChartInitializer', - ]; - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php deleted file mode 100644 index 7ec370a767d9e8143b880e995354e7fff0e9a813..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * The AbstractCtaButtonWidget class is the basic widget class for simple CTA widgets. - * It is possible to extend this class for custom widgets. - * Simply overwrite $this->link and $this->label to make use of this widget type. - */ -abstract class AbstractCtaButtonWidget extends AbstractWidget -{ - /** - * This link will be the main data in the widget - * - * @var string - */ - protected $link = ''; - - /** - * When filled, this is used as the button label - * - * @var string - */ - protected $label = ''; - - /** - * When filled, a text is shown above the link - * - * @var string - */ - protected $text = ''; - - /** - * This property contains the identifier of the icon that should be shown in the widget - * - * @var string - */ - protected $icon; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-calltoaction'; - - /** - * @var string - */ - protected $templateName = 'CtaWidget'; - - public function __construct(string $identifier) - { - parent::__construct($identifier); - $this->height = 1; - - $this->view->assignMultiple([ - 'link' => $this->link, - 'label' => $this->label, - 'icon' => $this->icon, - 'text' => $this->getLanguageService()->sL($this->text) - ]); - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php deleted file mode 100644 index c5b5b79f70f174bc231f07e3fd5bc71b890c1653..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * The AbstractDoughnutChartWidget class is the basic widget class for doughnut charts. - * It is possible to extend this class for custom widgets. - * In your class you have to store the data to display in $this->chartData. - * More information can be found in the documentation. - */ -abstract class AbstractDoughnutChartWidget extends AbstractChartWidget -{ - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-chart-pie'; - - /** - * @var string - */ - protected $chartType = 'doughnut'; - - /** - * @var mixed[] - */ - protected $chartOptions = [ - 'maintainAspectRatio' => false, - 'legend' => [ - 'display' => true, - 'position' => 'bottom' - ], - 'cutoutPercentage' => 60 - ]; - - /** - * @var int - */ - protected $height = 4; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php deleted file mode 100644 index 09f3d1ed228100fddc4f8b0505616f2185f27ca1..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * The AbstractListWidget class is the basic widget class for structured content. - * It is possible to extend this class for custom widgets. - * In your class you have to set $this->items with the data to display. - */ -abstract class AbstractListWidget extends AbstractWidget -{ - /** - * This property should contain the items to be displayed - * - * @var array - */ - protected $items = []; - - /** - * Limit the amount of items to be displayed - * - * @var int - */ - protected $limit = 5; - - /** - * Link to e.g. an external resource with the full items list - * - * @var string - */ - protected $moreItemsLink = ''; - - /** - * This should be the text for the more items link - * - * @var string - */ - protected $moreItemsText = ''; - - /** - * @var int - */ - protected $height = 4; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-list'; - - /** - * @var string - */ - protected $templateName = 'ListWidget'; - - public function renderWidgetContent(): string - { - $this->view->assign('items', $this->items); - $this->view->assign('moreItemsLink', $this->moreItemsLink); - $this->view->assign('moreItemsText', $this->moreItemsText); - return $this->view->render(); - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php deleted file mode 100644 index cb1672939f385b50e814008c6c586c95bd11d1e9..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * The AbstractNumberWithIconWidget class is the basic widget class to display a number next to an icon. - * It is possible to extend this class for own widgets. - * Simply overwrite $this->subtitle, $this->number and $this->icon to make use of this widget type. - */ -abstract class AbstractNumberWithIconWidget extends AbstractWidget -{ - /** - * When filled, a subtitle is shown below the title - * - * @var string - */ - protected $subtitle; - - /** - * This number will be the main data in the widget - * - * @var int - */ - protected $number; - - /** - * This property contains the identifier of the icon that should be shown in the widget - * - * @var string - */ - protected $icon; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-number'; - - /** - * @var string - */ - protected $templateName = 'NumberWithIconWidget'; - - protected function initializeView(): void - { - parent::initializeView(); - $this->view->assign('icon', $this->icon); - $this->view->assign('subtitle', $this->getSubTitle()); - $this->view->assign('number', $this->number); - } - - public function getSubTitle(): string - { - return $this->getLanguageService()->sL($this->subtitle) ?: $this->subtitle; - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php deleted file mode 100644 index 55a2be2983e15f1a81bf306a6f6dec18f5b22d22..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php +++ /dev/null @@ -1,105 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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 RuntimeException; -use TYPO3\CMS\Core\Cache\CacheManager; -use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; -use TYPO3\CMS\Core\Utility\GeneralUtility; - -/** - * The AbstractRssWidget class is the basic widget class to display items from a RSS feed. - * It is possible to extend this class for custom widgets. - * In your class you have to set $this->rssFile with the URL to the feed. - */ -abstract class AbstractRssWidget extends AbstractListWidget -{ - /** - * The rss file (resource) the data should be fetched from - * - * @var string - */ - protected $rssFile = ''; - - /** - * Lifetime of the items cache - * - * @var int - */ - protected $lifeTime = 900; - - /** - * @var int - */ - protected $height = 6; - - /** - * @var int - */ - protected $width = 4; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-rss'; - - /** - * @var string - */ - protected $templateName = 'RssWidget'; - - /** - * @var FrontendInterface - */ - protected $cache; - - public function __construct(string $identifier) - { - AbstractListWidget::__construct($identifier); - $this->cache = GeneralUtility::makeInstance(CacheManager::class) - ->getCache('dashboard_rss'); - $this->loadRssFeed(); - } - - protected function loadRssFeed(): void - { - $cacheHash = md5($this->rssFile); - if ($this->items = $this->cache->get($cacheHash)) { - return; - } - - $rssContent = GeneralUtility::getUrl($this->rssFile); - if ($rssContent === false) { - throw new RuntimeException('RSS URL could not be fetched', 1573385431); - } - $rssFeed = simplexml_load_string($rssContent); - $itemCount = 0; - foreach ($rssFeed->channel->item as $item) { - if ($itemCount < $this->limit) { - $this->items[] = [ - 'title' => (string)$item->title, - 'link' => trim((string)$item->link), - 'pubDate' => (string)$item->pubDate, - 'description' => (string)$item->description, - ]; - } else { - continue; - } - $itemCount++; - } - $this->cache->set($cacheHash, $this->items, ['dashboard_rss'], $this->lifeTime); - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php deleted file mode 100644 index c55f7f7c04d765f881e4f483f3b112023bad422d..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php +++ /dev/null @@ -1,164 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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\Localization\LanguageService; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; -use TYPO3\CMS\Fluid\View\StandaloneView; -use TYPO3Fluid\Fluid\View\ViewInterface; - -/** - * The AbstractWidget class is the basic widget class for all widgets. - * It is possible to extend this class for custom widgets, but EXT:dashboard provides - * some more specific types of widgets to extend from. For more details, please check: - * - * @see AbstractBarChartWidget - * @see AbstractChartWidget - * @see AbstractCtaButtonWidget - * @see AbstractDoughnutChartWidget - * @see AbstractListWidget - * @see AbstractNumberWithIconWidget - * @see AbstractRssWidget - */ -abstract class AbstractWidget implements WidgetInterface -{ - /** - * The unique identifier of the widget - * - * @var string - */ - protected $identifier; - - /** - * The title is used for the widget selector - * - * @var string - */ - protected $title; - - /** - * The description is used for the widget selector - * - * @var string - */ - protected $description = ''; - - /** - * The height of the widget in rows (1-6) - * - * @var int - */ - protected $height = 2; - - /** - * The width of the widget in rows (1-4) - * - * @var int - */ - protected $width = 2; - - /** - * The icon identifier is used for the widget selector - * - * @var string - */ - protected $iconIdentifier = ''; - - /** - * The template name of the widget - * - * @var string - */ - protected $templateName = 'Widget'; - - /** - * Additional CSS classes which should be added to the rendered widget - * - * @var string - */ - protected $additionalClasses = ''; - - /** - * @var ViewInterface - */ - protected $view; - - public function __construct(string $identifier) - { - $this->identifier = $identifier; - - $this->initializeView(); - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getTitle(): string - { - return $this->getLanguageService()->sL($this->title) ?: $this->title; - } - - public function getDescription(): string - { - return $this->getLanguageService()->sL($this->description) ?: $this->description; - } - - public function getIconIdentifier(): string - { - return $this->iconIdentifier; - } - - public function getHeight(): int - { - return $this->height; - } - - public function getWidth(): int - { - return $this->width; - } - - public function renderWidgetContent(): string - { - return $this->view->render(); - } - - public function getAdditionalClasses(): string - { - return $this->additionalClasses; - } - - protected function initializeView(): void - { - $this->view = GeneralUtility::makeInstance(StandaloneView::class); - $this->view->setTemplate('Widget/' . $this->templateName); - - $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%m-%d-%Y' : '%d-%m-%Y'; - $this->view->assign('dateFormat', $dateFormat); - - $this->view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('dashboard'); - - $this->view->assign('title', $this->getTitle()); - } - - protected function getLanguageService(): LanguageService - { - return $GLOBALS['LANG']; - } -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..66052161b8924328528ef21fd85025b20221a374 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php @@ -0,0 +1,135 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Utility\ButtonUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete Bar Chart widget implementation + * + * Shows a widget with a bar chart. The data for this chart will be provided by the data provider you will set. + * You can add a button to the widget by defining a button provider. + * + * There are no options available for this widget + * + * @see ChartDataProviderInterface + * @see ButtonProviderInterface + */ +class BarChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface, RequireJsModuleInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + + /** + * @var ChartDataProviderInterface + */ + private $dataProvider; + + /** + * @var StandaloneView + */ + private $view; + + /** + * @var ButtonProviderInterface|null + */ + private $buttonProvider; + + /** + * @var array + */ + private $options; + + public function __construct( + WidgetConfigurationInterface $configuration, + ChartDataProviderInterface $dataProvider, + StandaloneView $view, + $buttonProvider = null, + array $options = [] + ) { + $this->configuration = $configuration; + $this->dataProvider = $dataProvider; + $this->view = $view; + $this->options = $options; + $this->buttonProvider = $buttonProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/ChartWidget'); + $this->view->assignMultiple([ + 'button' => ButtonUtility::generateButtonConfig($this->buttonProvider), + 'options' => $this->options, + 'configuration' => $this->configuration, + ]); + return $this->view->render(); + } + + public function getEventData(): array + { + return [ + 'graphConfig' => [ + 'type' => 'bar', + 'options' => [ + 'maintainAspectRatio' => false, + 'legend' => [ + 'display' => false, + ], + 'scales' => [ + 'yAxes' => [ + [ + 'ticks' => [ + 'beginAtZero' => true, + ], + ], + ], + 'xAxes' => [ + [ + 'ticks' => [ + 'maxTicksLimit' => 15, + ], + ], + ], + ], + ], + 'data' => $this->dataProvider->getChartData(), + ], + ]; + } + + public function getCssFiles(): array + { + return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css']; + } + + public function getRequireJsModules(): array + { + return [ + 'TYPO3/CMS/Dashboard/Contrib/chartjs', + 'TYPO3/CMS/Dashboard/ChartInitializer', + ]; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php b/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..aeb490d7894050de62303d2336718b15efe06f03 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php @@ -0,0 +1,81 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Utility\ButtonUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete CTA button implementation + * + * Shows a widget with a CTA button to easily go to a specific page or do a specific action. You can add a button to the + * widget by defining a button provider. + * + * The following options are available during registration: + * - text string Adds a text to the widget to give some more background information about + * what a user can expect when clicking the button. You can either enter a + * normal string or a translation string (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text) + * @see ButtonProviderInterface + */ +class CtaWidget implements WidgetInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + + /** + * @var StandaloneView + */ + private $view; + + /** + * @var array + */ + private $options; + + /** + * @var ButtonProviderInterface|null + */ + private $buttonProvider; + + public function __construct( + WidgetConfigurationInterface $configuration, + StandaloneView $view, + $buttonProvider = null, + array $options = [] + ) { + $this->configuration = $configuration; + $this->view = $view; + $this->options = array_merge(['text' => ''], $options); + $this->buttonProvider = $buttonProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/CtaWidget'); + $this->view->assignMultiple([ + 'text' => $this->options['text'], + 'options' => $this->options, + 'button' => ButtonUtility::generateButtonConfig($this->buttonProvider), + 'configuration' => $this->configuration + ]); + return $this->view->render(); + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php deleted file mode 100644 index 9f4b4190e8d3436d7d3e119e3a44508f27f58f2f..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * This widget will show a link to the documentation of TYPO3 to make it easier for people - * to find the right documentation. - */ -class DocumentationGettingStartedWidget extends AbstractCtaButtonWidget -{ - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.title'; - - /** - * @var string - */ - protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.description'; - - /** - * @var string - */ - protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.content.label'; - - /** - * @var string - */ - protected $link = 'https://docs.typo3.org/m/typo3/tutorial-getting-started/master/en-us/Index.html'; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-text'; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php deleted file mode 100644 index c3038d202462b7c08b8a545fcd1087162b472041..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * This widget will show a link to the documentation of TYPO3 to make it easier for people - * to find the right documentation. - */ -class DocumentationTSconfigReferenceWidget extends AbstractCtaButtonWidget -{ - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.title'; - - /** - * @var string - */ - protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.text'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.description'; - - /** - * @var string - */ - protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.content.label'; - - /** - * @var string - */ - protected $link = 'https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/Index.html'; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-text'; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php deleted file mode 100644 index b816da81b2e45b065f7f2de5a11c0d101f0fd411..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * This widget will show a link to the documentation of TYPO3 to make it easier for people - * to find the right documentation. - */ -class DocumentationTypoScriptReference extends AbstractCtaButtonWidget -{ - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.title'; - - /** - * @var string - */ - protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.text'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.description'; - - /** - * @var string - */ - protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.content.label'; - - /** - * @var string - */ - protected $link = 'https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Index.html'; - - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-text'; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..8a2860302035641db93609d590cd8995b6c3d22f --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php @@ -0,0 +1,121 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Utility\ButtonUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete Doughnut Chart widget implementation + * + * Shows a widget with a doughnut chart. The data for this chart will be provided by the data provider you will set. + * You can add a button to the widget by defining a button provider. + * + * There are no options available for this widget + * + * @see ChartDataProviderInterface + * @see ButtonProviderInterface + */ +class DoughnutChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface, RequireJsModuleInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + + /** + * @var ChartDataProviderInterface + */ + private $dataProvider; + + /** + * @var StandaloneView + */ + private $view; + + /** + * @var ButtonProviderInterface|null + */ + private $buttonProvider; + + /** + * @var array + */ + private $options; + + public function __construct( + WidgetConfigurationInterface $configuration, + ChartDataProviderInterface $dataProvider, + StandaloneView $view, + $buttonProvider = null, + array $options = [] + ) { + $this->configuration = $configuration; + $this->dataProvider = $dataProvider; + $this->view = $view; + $this->options = $options; + $this->buttonProvider = $buttonProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/ChartWidget'); + $this->view->assignMultiple([ + 'button' => ButtonUtility::generateButtonConfig($this->buttonProvider), + 'options' => $this->options, + 'configuration' => $this->configuration, + ]); + return $this->view->render(); + } + + public function getEventData(): array + { + return [ + 'graphConfig' => [ + 'type' => 'doughnut', + 'options' => [ + 'maintainAspectRatio' => false, + 'legend' => [ + 'display' => true, + 'position' => 'bottom' + ], + 'cutoutPercentage' => 60 + ], + 'data' => $this->dataProvider->getChartData(), + ], + ]; + } + + public function getCssFiles(): array + { + return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css']; + } + + public function getRequireJsModules(): array + { + return [ + 'TYPO3/CMS/Dashboard/Contrib/chartjs', + 'TYPO3/CMS/Dashboard/ChartInitializer', + ]; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php index 83d152c7bd606381a63419d48333c7df833ddc98..2e7e95c359ad4e46b94ec6078c186c85938e4908 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php @@ -2,8 +2,20 @@ declare(strict_types = 1); namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + /** - * Interface AdditionalCssInterface * In case a widget should provide additional CSS files, the widget must implement this interface. */ interface AdditionalCssInterface @@ -11,6 +23,7 @@ interface AdditionalCssInterface /** * This method returns an array with paths to required CSS files. * e.g. ['EXT:myext/Resources/Public/Css/my_widget.css'] + * * @return array */ public function getCssFiles(): array; diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php index 6691a2bf56d4d2be01864f98922894fc42bcce4a..acb38d693233436d2bb8cd9087361a708f2a2b5f 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php @@ -2,8 +2,20 @@ declare(strict_types = 1); namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + /** - * Interface AdditionalJavaScriptInterface * In case a widget should provide additional JavaScript files, the widget must implement this interface. */ interface AdditionalJavaScriptInterface @@ -11,6 +23,7 @@ interface AdditionalJavaScriptInterface /** * This method returns an array with paths to required JS files. * e.g. ['EXT:myext/Resources/Public/JavaScript/my_widget.js'] + * * @return array */ public function getJsFiles(): array; diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a308a6ade61565cf596b245c1e994c7132c7ab8a --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php @@ -0,0 +1,45 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; + +/* + * 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! + */ + +/** + * In case a widget should have a button in the footer of the widget, this button must implement this interface. + */ +interface ButtonProviderInterface +{ + /** + * This method should return the title that will be shown as the text on the button. As the title will be + * translated within the template, you can also return a localization string like + * 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:button' + * + * @return string + */ + public function getTitle(): string; + + /** + * Return the link + * + * @return string + */ + public function getLink(): string; + + /** + * Specify the target of the link like '_blank' + * + * @return string + */ + public function getTarget(): string; +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..78ee384ba8e8b749006e66b66948a3edbb091c41 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php @@ -0,0 +1,34 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; + +/* + * 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! + */ + +/** + * Defines API for provider, used for chart widgets. + */ +interface ChartDataProviderInterface +{ + /** + * This method should provide the data for the graph. + * The data and options you have depend on the type of chart. + * More information can be found in the documentation of the specific type. + * + * @link https://www.chartjs.org/docs/latest/charts/bar.html#data-structure + * @link https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure + * + * @return array + */ + public function getChartData(): array; +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php index fae69e9c6d567955b3041639ef399d9f7f3b4694..b8ee4398c5b346d5b3e25be34fb6be483b170793 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php @@ -2,14 +2,27 @@ declare(strict_types = 1); namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + /** - * Interface EventDataInterface * In case a widget should provide additional data as JSON payload, the widget must implement this interface. */ interface EventDataInterface { /** * This method returns data which should be send to the widget as JSON encoded value. + * * @return array */ public function getEventData(): array; diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e2899767c21ce3740ded77fd39b18a6711705438 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php @@ -0,0 +1,30 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; + +/* + * 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! + */ + +/** + * The dataprovider of a ListWidget, should implement this interface + */ +interface ListDataProviderInterface +{ + /** + * Return the items to be shown. This should be an array like ['item 1', 'item 2', 'item 3']. This is a + * real simple list of items. + * + * @return array + */ + public function getItems(): array; +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..21e6dbb4a6f1214776da0092c04294ee735b34ac --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php @@ -0,0 +1,29 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; + +/* + * 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! + */ + +/** + * The dataprovider of a NumberWithIcon widget should implement this interface + */ +interface NumberWithIconDataProviderInterface +{ + /** + * Return the number that should be shown in the widget + * + * @return int + */ + public function getNumber(): int; +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php index 7add78da29a705c97a6c3030b14bbfc24cf26727..71071bc4efcc55e33b5c917376fc953f38207b81 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php @@ -2,8 +2,20 @@ declare(strict_types = 1); namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + /** - * Interface RequireJsModuleInterface * In case a widget should provide additional requireJS modules, the widget must implement this interface. */ interface RequireJsModuleInterface @@ -14,6 +26,7 @@ interface RequireJsModuleInterface * 'TYPO3/CMS/Backend/Modal', * 'TYPO3/CMS/MyExt/FooBar' => 'function(FooBar) { ... }' * ] + * * @return array */ public function getRequireJsModules(): array; diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..37e0964f56eb5229e4014ca7967a4cedcfde85e1 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php @@ -0,0 +1,87 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; + +/* + * 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! + */ + +/** + * Defines API of configuration for a widget. + * This is separated by concrete implementation of a widget (WidgetInterface). + * The configuration is used to generate UX, and other stuff. + */ +interface WidgetConfigurationInterface +{ + /** + * Returns the unique identifer of a widget + * + * @return string + */ + public function getIdentifier(): string; + + /** + * Returns the service name providing the widget implementation + * + * @return string + */ + public function getServiceName(): string; + + /** + * Returns array of group names associated to this widget + * + * @return array + */ + public function getGroupNames(): array; + + /** + * Returns the title of a widget, this is used for the widget selector + * + * @return string + */ + public function getTitle(): string; + + /** + * Returns the description of a widget, this is used for the widget selector + * + * @return string + */ + public function getDescription(): string; + + /** + * Returns the icon identifier of a widget, this is used for the widget selector + * + * @return string + */ + public function getIconIdentifier(): string; + + /** + * Returns the height of a widget (small, medium, large) + * + * @return string + */ + public function getHeight(): string; + + /** + * Returns the width of a widget (small, medium, large) + * + * @return string + */ + public function getWidth(): string; + + /** + * This method returns additional CSS classes which should be added to the rendered widget + * + * @return string + */ + public function getAdditionalCssClasses(): string; +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php index b122bb81d20794ab4e59c3027c85c8a40a75b354..c59c6521d801719d53bf5fb28e916b7d0662e8f0 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php @@ -2,6 +2,19 @@ declare(strict_types = 1); namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; +/* + * 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! + */ + /** * The WidgetInterface is the base interface for all kind of widgets. * All widgets must implement this interface. @@ -9,54 +22,12 @@ namespace TYPO3\CMS\Dashboard\Widgets\Interfaces; */ interface WidgetInterface { - /** - * Returns the unique identifer of a widget - * - * @return string - */ - public function getIdentifier(): string; - - /** - * Returns the title of a widget, this is used for the widget selector - * @return string - */ - public function getTitle(): string; - - /** - * Returns the description of a widget, this is used for the widget selector - * @return string - */ - public function getDescription(): string; - - /** - * Returns the icon identifier of a widget, this is used for the widget selector - * @return string - */ - public function getIconIdentifier(): string; - - /** - * Returns the height of a widget in rows (1-6) - * @return int - */ - public function getHeight(): int; - - /** - * Returns the width of a widget in columns (1-4) - * @return int - */ - public function getWidth(): int; - /** * This method returns the content of a widget. The returned markup will be delivered * by an AJAX call and will not be escaped. * Be aware of XSS and ensure that the content is well encoded. + * * @return string */ public function renderWidgetContent(): string; - - /** - * This method returns additional CSS classes which should be added to the rendered widget - * @return string - */ - public function getAdditionalClasses(): string; } diff --git a/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php b/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..5ca2ea49f8e3005d322b20b62fef0e7739c627b0 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php @@ -0,0 +1,106 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Widgets\Interfaces\ButtonProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ListDataProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete List Widget implementation + * + * The widget will show a simple list with items provided by a data provider. You can add a button to the widget by + * defining a button provider. + * + * There are no options available for this widget + * + * @see ListDataProviderInterface + * @see ButtonProviderInterface + */ +class ListWidget implements WidgetInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + + /** + * @var StandaloneView + */ + private $view; + + /** + * @var array + */ + private $options; + /** + * @var ButtonProviderInterface|null + */ + private $buttonProvider; + + /** + * @var ListDataProviderInterface + */ + private $dataProvider; + + public function __construct( + WidgetConfigurationInterface $configuration, + ListDataProviderInterface $dataProvider, + StandaloneView $view, + $buttonProvider = null, + array $options = [] + ) { + $this->configuration = $configuration; + $this->view = $view; + $this->options = $options; + $this->buttonProvider = $buttonProvider; + $this->dataProvider = $dataProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/ListWidget'); + $this->view->assignMultiple([ + 'items' => $this->getItems(), + 'options' => $this->options, + 'button' => $this->getButton(), + 'configuration' => $this->configuration, + ]); + return $this->view->render(); + } + + protected function getItems(): array + { + return $this->dataProvider->getItems(); + } + + private function getButton(): array + { + $button = []; + + if ($this->buttonProvider instanceof ButtonProviderInterface && $this->buttonProvider->getTitle() && $this->buttonProvider->getLink()) { + $button = [ + 'text' => $this->buttonProvider->getTitle(), + 'link' => $this->buttonProvider->getLink(), + 'target' => $this->buttonProvider->getTarget(), + ]; + } + + return $button; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php b/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..41446748d217df98fb39a94a12a19a296ce2e35b --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php @@ -0,0 +1,88 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Widgets\Interfaces\NumberWithIconDataProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete Number with Icon implementation + * + * The widget will show widget with an icon, a number, a title and a subtitle. The number is provided by a data + * provider. + * + * The following options are available during registration: + * - icon string The icon-identifier of the icon that should be shown in the widget. You should + * register your icon with the Icon API + * - title string The main title that will be shown in the widget as an explanation of the shown number. + * You can either enter a normal string or a translation string + * (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title) + * - subtitle string The subtitle that will give some additional information about the number and title. + * You can either enter a normal string or a translation string + * (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle) + * + * @see NumberWithIconDataProviderInterface + */ +class NumberWithIconWidget implements WidgetInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + /** + * @var StandaloneView + */ + private $view; + /** + * @var array + */ + private $options; + /** + * @var NumberWithIconDataProviderInterface + */ + private $dataProvider; + + public function __construct( + WidgetConfigurationInterface $configuration, + NumberWithIconDataProviderInterface $dataProvider, + StandaloneView $view, + array $options = [] + ) { + $this->configuration = $configuration; + $this->view = $view; + $this->options = $options; + $this->dataProvider = $dataProvider; + } + + /** + * @inheritDoc + */ + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/NumberWithIconWidget'); + $this->view->assignMultiple([ + 'icon' => $this->options['icon'], + 'title' => $this->options['title'], + 'subtitle' => $this->options['subtitle'], + 'number' => $this->dataProvider->getNumber(), + 'options' => $this->options, + 'configuration' => $this->configuration + ]); + return $this->view->render(); + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..5a8a9d812489c80b90067fd95adaad5886bec25e --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php @@ -0,0 +1,63 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Provider; + +/* + * 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\Dashboard\Widgets\Interfaces\ButtonProviderInterface; + +/** + * Provide link for sys log button. + * Check whether belog is enabled and add link to module. + * No link is returned if not enabled. + */ +class ButtonProvider implements ButtonProviderInterface +{ + /** + * @var string + */ + private $title; + + /** + * @var string + */ + private $target; + + /** + * @var string + */ + private $link; + + public function __construct(string $title, string $link, string $target = '') + { + $this->title = $title; + $this->target = $target; + $this->link = $link; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getLink(): string + { + return $this->link; + } + + public function getTarget(): string + { + return $this->target; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php similarity index 66% rename from typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php rename to typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php index 3ed002e9bf0b6a705bd13f54c3a18707cea98f51..159c52b03a2d7ffc6c863c6431d0a66ad58c854d 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php @@ -1,6 +1,6 @@ <?php declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; +namespace TYPO3\CMS\Dashboard\Widgets\Provider; /* * This file is part of the TYPO3 CMS project. @@ -21,39 +21,11 @@ use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction; use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification; use TYPO3\CMS\Core\SysLog\Type as SystemLogType; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\NumberWithIconDataProviderInterface; -/** - * This widget will show the number of failed logins during a given period - */ -class FailedLoginsWidget extends AbstractNumberWithIconWidget +class NumberOfFailedLoginsDataProvider implements NumberWithIconDataProviderInterface { - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description'; - - /** - * @var string - */ - protected $subtitle = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle'; - - /** - * @var string - */ - protected $icon = 'content-elements-login'; - - protected function initializeView(): void - { - $this->number = $this->getNumberOfFailedLogins(); - parent::initializeView(); - } - - public function getNumberOfFailedLogins(int $secondsBack = 86400): int + public function getNumber(int $secondsBack = 86400): int { $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log'); diff --git a/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..6be6af5c5a9d4fd2f57b3c4991391f71f4432498 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php @@ -0,0 +1,68 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets\Provider; + +/* + * 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\Routing\UriBuilder; +use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface; + +/** + * Provide link for sys log button. + * Check whether belog is enabled and add link to module. + * No link is returned if not enabled. + */ +class SysLogButtonProvider implements ButtonProviderInterface +{ + /** + * @var string + */ + private $title; + + /** + * @var string + */ + private $target; + + public function __construct(string $title, string $target = '') + { + $this->title = $title; + $this->target = $target; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getLink(): string + { + if (ExtensionManagementUtility::isLoaded('belog')) { + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + return (string)$uriBuilder->buildUriFromRoute( + 'system_BelogLog', + ['tx_belog_system_beloglog[constraint][action]' => -1] + ); + } + + return ''; + } + + public function getTarget(): string + { + return $this->target; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php similarity index 61% rename from typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php rename to typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php index 96f16eedcaa216740d7938df34d9f28f88be4530..0a6b70c0dd96138581d18bd73e77ce73adf5183e 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php @@ -1,6 +1,6 @@ <?php declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; +namespace TYPO3\CMS\Dashboard\Widgets\Provider; /* * This file is part of the TYPO3 CMS project. @@ -15,78 +15,54 @@ namespace TYPO3\CMS\Dashboard\Widgets; * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\SysLog\Type as SystemLogType; -use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\WidgetApi; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface; /** - * This widget will show the number of system log errors in a bar chart + * Provides chart data for sys log errors. */ -class SysLogErrorsWidget extends AbstractBarChartWidget +class SysLogErrorsDataProvider implements ChartDataProviderInterface { /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description'; - - /** - * @var string - */ - protected $buttonText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText'; - - /** + * Number of days to gather information for. + * * @var int */ - protected $width = 4; + protected $days = 31; /** - * @var int + * @var array */ - protected $height = 4; + protected $labels = []; /** - * @var mixed[] + * @var array */ protected $data = []; - /** - * @var mixed[] - */ - protected $labels = []; - - protected function initializeView(): void + public function __construct(int $days = 31) { - if (ExtensionManagementUtility::isLoaded('belog')) { - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); - $this->buttonLink = (string)$uriBuilder->buildUriFromRoute( - 'system_BelogLog', - ['tx_belog_system_beloglog[constraint][action]' => -1] - ); - } - - parent::initializeView(); + $this->days = $days; } + /** * @inheritDoc */ - protected function prepareChartData(): void + public function getChartData(): array { - $this->calculateDataForLastDays(31); + $this->calculateDataForLastDays(); - $this->chartData = [ + return [ 'labels' => $this->labels, 'datasets' => [ [ 'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'), - 'backgroundColor' => $this->chartColors[0], + 'backgroundColor' => WidgetApi::getDefaultChartColors()[0], 'border' => 0, 'data' => $this->data ] @@ -118,11 +94,11 @@ class SysLogErrorsWidget extends AbstractBarChartWidget ->fetchColumn(); } - protected function calculateDataForLastDays(int $days): void + protected function calculateDataForLastDays(): void { $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d'; - for ($daysBefore=$days; $daysBefore>=0; $daysBefore--) { + for ($daysBefore = $this->days; $daysBefore >= 0; $daysBefore--) { $this->labels[] = date($format, strtotime('-' . $daysBefore . ' day')); $startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00'); $endPeriod = strtotime('-' . $daysBefore . ' day 23:59:59'); @@ -130,4 +106,9 @@ class SysLogErrorsWidget extends AbstractBarChartWidget $this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod); } } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } } diff --git a/typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php similarity index 61% rename from typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php rename to typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php index 4b627d4649b9b4c552effb13173f9397890e3c6b..06d330b7f1f8bcd45ce1f9939f6c99a1b92583e1 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php +++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php @@ -1,6 +1,6 @@ <?php declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; +namespace TYPO3\CMS\Dashboard\Widgets\Provider; /* * This file is part of the TYPO3 CMS project. @@ -17,39 +17,39 @@ namespace TYPO3\CMS\Dashboard\Widgets; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\WidgetApi; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface; -/** - * This widget will show the type of users (admin / non-admin) in a doughnut chart - */ -class TypeOfUsersWidget extends AbstractDoughnutChartWidget +class TypeOfUsersChartDataProvider implements ChartDataProviderInterface { /** - * @var string + * @var LanguageService */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title'; + private $languageService; - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description'; + public function __construct(LanguageService $languageService) + { + $this->languageService = $languageService; + } /** * @inheritDoc */ - protected function prepareChartData(): void + public function getChartData(): array { $adminUsers = $this->getNumberOfUsers(true); $normalUsers = $this->getNumberOfUsers(false); - $this->chartData = [ + return [ 'labels' => [ - $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'), - $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers') + $this->languageService ->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'), + $this->languageService ->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers') ], 'datasets' => [ [ - 'backgroundColor' => [$this->chartColors[0], $this->chartColors[1]], + 'backgroundColor' => WidgetApi::getDefaultChartColors(), 'data' => [$normalUsers, $adminUsers] ] ], diff --git a/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php b/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..14ba8673d04dc094169a468fe60b23afafa07f3a --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php @@ -0,0 +1,144 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Cache\Frontend\FrontendInterface as Cache; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; + +/** + * Concrete RSS widget implementation + * + * The widget will show a certain number of items of the given RSS feed. The feed will be set by the feedUrl option. You + * can add a button to the widget by defining a button provider. + * + * The following options are available during registration: + * - feedUrl string Defines the URL or file providing the RSS Feed. + * This is read by the widget in order to fetch entries to show. + * - limit int default: 5 Defines how many RSS items should be shown. + * - lifetime int default: 43200 Defines how long to wait, in seconds, until fetching RSS Feed again + * + * @see ButtonProviderInterface + */ +class RssWidget implements WidgetInterface +{ + /** + * @var WidgetConfigurationInterface + */ + private $configuration; + + /** + * @var StandaloneView + */ + private $view; + + /** + * @var Cache + */ + private $cache; + + /** + * @var array + */ + private $options; + + /** + * @var ButtonProviderInterface|null + */ + private $buttonProvider; + + public function __construct( + WidgetConfigurationInterface $configuration, + Cache $cache, + StandaloneView $view, + $buttonProvider = null, + array $options = [] + ) { + $this->configuration = $configuration; + $this->view = $view; + $this->cache = $cache; + $this->options = array_merge( + [ + 'limit' => 5, + 'lifeTime' => 43200 + ], + $options + ); + $this->buttonProvider = $buttonProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/RssWidget'); + $this->view->assignMultiple([ + 'items' => $this->getRssItems(), + 'options' => $this->options, + 'button' => $this->getButton(), + 'configuration' => $this->configuration, + ]); + return $this->view->render(); + } + + protected function getRssItems(): array + { + $cacheHash = md5($this->options['feedUrl']); + if ($items = $this->cache->get($cacheHash)) { + return $items; + } + + $rssContent = GeneralUtility::getUrl($this->options['feedUrl']); + if ($rssContent === false) { + throw new \RuntimeException('RSS URL could not be fetched', 1573385431); + } + $rssFeed = simplexml_load_string($rssContent); + $itemCount = 0; + $items = []; + foreach ($rssFeed->channel->item as $item) { + if ($itemCount >= $this->options['limit']) { + break; + } + + $items[] = [ + 'title' => (string)$item->title, + 'link' => trim((string)$item->link), + 'pubDate' => (string)$item->pubDate, + 'description' => (string)$item->description, + ]; + $itemCount++; + } + $this->cache->set($cacheHash, $items, ['dashboard_rss'], $this->options['lifeTime']); + + return $items; + } + + private function getButton(): array + { + $button = []; + + if ($this->buttonProvider instanceof ButtonProviderInterface && $this->buttonProvider->getTitle() && $this->buttonProvider->getLink()) { + $button = [ + 'text' => $this->buttonProvider->getTitle(), + 'link' => $this->buttonProvider->getLink(), + 'target' => $this->buttonProvider->getTarget(), + ]; + } + + return $button; + } +} diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php b/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php similarity index 54% rename from typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php rename to typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php index 2dd249edf553b1ce027c2749032ebb5fe4b0cb1f..6d31f3eb0e45d27109fe038a582ad4e76d7af3a6 100644 --- a/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php +++ b/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php @@ -17,53 +17,58 @@ namespace TYPO3\CMS\Dashboard\Widgets; use TYPO3\CMS\Core\Information\Typo3Information; use TYPO3\CMS\Core\Information\Typo3Version; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; /** - * This widget will show general information regarding TYPO3 + * Concrete TYPO3 information widget + * + * This widget will give some general information about TYPO3 version and the version installed. + * + * There are no options available for this widget */ -class T3GeneralInformation extends AbstractWidget +class T3GeneralInformationWidget implements WidgetInterface { /** - * @var string + * @var WidgetConfigurationInterface */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.title'; + private $configuration; /** - * @var string + * @var StandaloneView */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.description'; + private $view; /** - * @var string + * @var array */ - protected $templateName = 'T3GeneralInformation'; + private $options; - /** - * @var string - */ - protected $iconIdentifier = 'content-widget-text'; - - /** - * @var int - */ - protected $height = 4; - - /** - * @var int - */ - protected $width = 4; + public function __construct( + WidgetConfigurationInterface $configuration, + StandaloneView $view, + array $options = [] + ) { + $this->configuration = $configuration; + $this->view = $view; + $this->options = $options; + } public function renderWidgetContent(): string { $typo3Information = new Typo3Information(); $typo3Version = new Typo3Version(); + + $this->view->setTemplate('Widget/T3GeneralInformationWidget'); $this->view->assignMultiple([ 'title' => 'TYPO3 CMS ' . $typo3Version->getVersion(), 'copyrightYear' => $typo3Information->getCopyrightYear(), 'currentVersion' => $typo3Version->getVersion(), 'donationUrl' => $typo3Information::URL_DONATE, 'copyRightNotice' => $typo3Information->getCopyrightNotice(), - + 'options' => $this->options, + 'configuration' => $this->configuration ]); return $this->view->render(); } diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php deleted file mode 100644 index 1ddbb57f75cbb9f28fac570a95ce86ef0e851332..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * This widget will show the latest news from the TYPO3 news RSS feed right - * on the dashboard. - */ -class T3NewsWidget extends AbstractRssWidget -{ - /** - * @var string - */ - protected $rssFile = 'https://www.typo3.org/rss'; - - /** - * @var int - */ - protected $lifeTime = 43200; // 12 hours cache - - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'; - - /** - * @var string - */ - protected $moreItemsLink = 'https://typo3.org/project/news'; - - /** - * @var string - */ - protected $moreItemsText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems'; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php b/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php deleted file mode 100644 index a33fa35cbb2f930a3384a68ce0d8baa0ee567bdb..0000000000000000000000000000000000000000 --- a/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -declare(strict_types = 1); -namespace TYPO3\CMS\Dashboard\Widgets; - -/* - * 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! - */ - -/** - * This widget will show the latest security advisories from the TYPO3 news RSS feed - * right on the dashboard. - */ -class T3SecurityAdvisoriesWidget extends AbstractRssWidget -{ - /** - * @var string - */ - protected $rssFile = 'https://typo3.org/?type=101'; - - /** - * @var int - */ - protected $lifeTime = 43200; // 12 hours cache - - /** - * @var string - */ - protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.title'; - - /** - * @var string - */ - protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.description'; - - /** - * @var string - */ - protected $moreItemsLink = 'https://typo3.org/help/security-advisories'; - - /** - * @var string - */ - protected $moreItemsText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.moreItems'; -} diff --git a/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php b/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php new file mode 100644 index 0000000000000000000000000000000000000000..008ca099cc77e3f1384a9c4277c5e2d4ffe11ed6 --- /dev/null +++ b/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php @@ -0,0 +1,144 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Dashboard\Widgets; + +/* + * 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\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; + +class WidgetConfiguration implements WidgetConfigurationInterface +{ + /** + * @var string + */ + private $identifier; + + /** + * @var string + */ + private $serviceName; + + /** + * @var array + */ + private $groupNames; + + /** + * @var string + */ + private $title; + + /** + * @var string + */ + private $description; + + /** + * @var string + */ + private $iconIdentifier; + + /** + * @var string + */ + private $height; + + /** + * @var string + */ + private $width; + + /** + * @var array + */ + private $additionalCssClasses; + + /** + * @throws \InvalidArgumentException If non valid parameters were provided. + */ + public function __construct( + string $identifier, + string $serviceName, + array $groupNames, + string $title, + string $description, + string $iconIdentifier, + string $height, + string $width, + array $additionalCssClasses + ) { + $allowedSizes = ['small', 'medium', 'large']; + if (!in_array($height, $allowedSizes, true)) { + throw new \InvalidArgumentException('Height of widgets has to be small, medium or large', 1584778196); + } + if (!in_array($height, $allowedSizes, true)) { + throw new \InvalidArgumentException('Width of widgets has to be small, medium or large', 1585249769); + } + + $this->identifier = $identifier; + $this->serviceName = $serviceName; + $this->groupNames = $groupNames; + $this->title = $title; + $this->description = $description; + $this->iconIdentifier = $iconIdentifier; + $this->height = $height; + $this->width = $width; + $this->additionalCssClasses = $additionalCssClasses; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function getServiceName(): string + { + return $this->serviceName; + } + + public function getGroupNames(): array + { + return $this->groupNames; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getIconIdentifier(): string + { + return $this->iconIdentifier; + } + + public function getHeight(): string + { + return $this->height; + } + + public function getWidth(): string + { + return $this->width; + } + + public function getAdditionalCssClasses(): string + { + return implode(' ', $this->additionalCssClasses); + } +} diff --git a/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml b/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5744d3be9948b5be1d50210a5f0703ed96c3c5bb --- /dev/null +++ b/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml @@ -0,0 +1,208 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3\CMS\Dashboard\Widgets\: + resource: '../Classes/Widgets/*' + + TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration: + autowire: false + + TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider: + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText' + + cache.dashboard.rss: + class: 'TYPO3\CMS\Core\Cache\Frontend\FrontendInterface' + factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] + arguments: + $identifier: 'dashboard_rss' + + dashboard.buttons.t3news: + class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider' + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems' + $link: 'https://typo3.org/project/news' + $target: '_blank' + + dashboard.buttons.t3securityAdvisories: + class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider' + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.moreItems' + $link: 'https://typo3.org/help/security-advisories' + $target: '_blank' + + dashboard.buttons.docGettingStarted: + class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider' + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.content.label' + $link: 'https://docs.typo3.org/m/typo3/tutorial-getting-started/master/en-us/Index.html' + $target: '_blank' + + dashboard.buttons.docTypoScriptReference: + class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider' + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.content.label' + $link: 'https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Index.html' + $target: '_blank' + + dashboard.buttons.docTSconfig: + class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider' + arguments: + $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.content.label' + $link: 'https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/Index.html' + $target: '_blank' + + dashboard.views.widget: + class: 'TYPO3\CMS\Fluid\View\StandaloneView' + public: true + factory: ['TYPO3\CMS\Dashboard\Views\Factory', 'widgetTemplate'] + + dashboard.widget.t3news: + class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget' + arguments: + $view: '@dashboard.views.widget' + $cache: '@cache.dashboard.rss' + $buttonProvider: '@dashboard.buttons.t3news' + $options: + feedUrl: 'https://www.typo3.org/rss' + tags: + - name: dashboard.widget + identifier: 't3news' + groupNames: 'typo3' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description' + iconIdentifier: 'content-widget-rss' + height: 'large' + width: 'medium' + + dashboard.widget.sysLogErrors: + class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget' + arguments: + $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider' + $view: '@dashboard.views.widget' + $buttonProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider' + tags: + - name: dashboard.widget + identifier: 'sysLogErrors' + groupNames: 'systemInfo' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description' + iconIdentifier: 'content-widget-chart-bar' + additionalCssClasses: 'dashboard-item--chart' + height: 'medium' + width: 'medium' + + dashboard.widget.docGettingStarted: + class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget' + arguments: + $view: '@dashboard.views.widget' + $buttonProvider: '@dashboard.buttons.docGettingStarted' + $options: + text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text' + tags: + - name: dashboard.widget + identifier: 'docGettingStarted' + groupNames: 'documentation' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.description' + iconIdentifier: 'content-widget-text' + height: 'small' + + dashboard.widget.docTypoScriptReference: + class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget' + arguments: + $view: '@dashboard.views.widget' + $buttonProvider: '@dashboard.buttons.docTypoScriptReference' + $options: + text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.text' + tags: + - name: dashboard.widget + identifier: 'docTypoScriptReference' + groupNames: 'documentation' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.description' + iconIdentifier: 'content-widget-text' + height: 'small' + + dashboard.widget.docTSconfig: + class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget' + arguments: + $view: '@dashboard.views.widget' + $buttonProvider: '@dashboard.buttons.docTSconfig' + $options: + text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.text' + tags: + - name: dashboard.widget + identifier: 'docTSconfig' + groupNames: 'documentation' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.description' + iconIdentifier: 'content-widget-text' + height: 'small' + + dashboard.widget.t3information: + class: 'TYPO3\CMS\Dashboard\Widgets\T3GeneralInformationWidget' + arguments: + $view: '@dashboard.views.widget' + tags: + - name: dashboard.widget + identifier: 't3information' + groupNames: 'general' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.description' + iconIdentifier: 'content-widget-text' + height: 'medium' + width: 'medium' + + dashboard.widget.typeOfUsers: + class: 'TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget' + arguments: + $view: '@dashboard.views.widget' + $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\TypeOfUsersChartDataProvider' + tags: + - name: dashboard.widget + identifier: 'typeOfUsers' + groupNames: 'systemInfo' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description' + iconIdentifier: 'content-widget-chart-pie' + additionalCssClasses: 'dashboard-item--chart' + height: 'large' + + dashboard.widget.t3securityAdvisories: + class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget' + arguments: + $view: '@dashboard.views.widget' + $cache: '@cache.dashboard.rss' + $buttonProvider: '@dashboard.buttons.t3securityAdvisories' + $options: + feedUrl: 'https://typo3.org/?type=101' + tags: + - name: dashboard.widget + identifier: 't3securityAdvisories' + groupNames: 'typo3' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.description' + iconIdentifier: 'content-widget-rss' + height: 'large' + width: 'medium' + + dashboard.widget.failedLogins: + class: 'TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget' + arguments: + $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\NumberOfFailedLoginsDataProvider' + $view: '@dashboard.views.widget' + $options: + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title' + subtitle: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle' + icon: 'content-elements-login' + tags: + - name: dashboard.widget + identifier: 'failedLogins' + groupNames: 'systemInfo' + title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title' + description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description' + iconIdentifier: 'content-widget-number' diff --git a/typo3/sysext/dashboard/Configuration/Services.yaml b/typo3/sysext/dashboard/Configuration/Services.yaml index 711d9111cb5dd45e68f59b9b8cfe5db1490aef16..cebad0f0d6f90b2103e739a9d31e054f8c47d8fc 100644 --- a/typo3/sysext/dashboard/Configuration/Services.yaml +++ b/typo3/sysext/dashboard/Configuration/Services.yaml @@ -1,3 +1,6 @@ +imports: + - { resource: Backend/DashboardWidgets.yaml } + services: _defaults: autowire: true @@ -6,6 +9,7 @@ services: TYPO3\CMS\Dashboard\: resource: '../Classes/*' + exclude: '../Classes/Widgets/*' TYPO3\CMS\Dashboard\Controller\DashboardController: public: true @@ -15,66 +19,3 @@ services: TYPO3\CMS\Dashboard\DasboardRegistry: public: true - - TYPO3\CMS\Dashboard\Widgets\DocumentationGettingStartedWidget: - arguments: ['docGettingStarted'] - tags: - - name: dashboard.widget - identifier: docGettingStarted - widgetGroups: 'documentation' - - TYPO3\CMS\Dashboard\Widgets\DocumentationTypoScriptReference: - arguments: ['docTypoScriptReference'] - tags: - - name: dashboard.widget - identifier: docTypoScriptReference - widgetGroups: 'documentation' - - TYPO3\CMS\Dashboard\Widgets\DocumentationTSconfigReferenceWidget: - arguments: ['docTSconfig'] - tags: - - name: dashboard.widget - identifier: docTSconfig - widgetGroups: 'documentation' - - TYPO3\CMS\Dashboard\Widgets\T3GeneralInformation: - arguments: ['t3information'] - tags: - - name: dashboard.widget - identifier: t3information - widgetGroups: 'general' - - TYPO3\CMS\Dashboard\Widgets\SysLogErrorsWidget: - arguments: ['sysLogErrors'] - tags: - - name: dashboard.widget - identifier: sysLogErrors - widgetGroups: 'systemInfo' - - TYPO3\CMS\Dashboard\Widgets\TypeOfUsersWidget: - arguments: ['typeOfUsers'] - tags: - - name: dashboard.widget - identifier: typeOfUsers - widgetGroups: 'systemInfo' - - TYPO3\CMS\Dashboard\Widgets\FailedLoginsWidget: - arguments: ['failedLogins'] - tags: - - name: dashboard.widget - identifier: failedLogins - widgetGroups: 'general' - - TYPO3\CMS\Dashboard\Widgets\T3NewsWidget: - arguments: ['t3news'] - tags: - - name: dashboard.widget - identifier: t3news - widgetGroups: 'typo3' - - TYPO3\CMS\Dashboard\Widgets\T3SecurityAdvisoriesWidget: - arguments: ['t3securityAdvisories'] - tags: - - name: dashboard.widget - identifier: t3securityAdvisories - widgetGroups: 'typo3' diff --git a/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html b/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html index d18e1f94d01254bc50e98b51ddda45d0b3c7acec..4af5719f7e748d300eec798e9c8995f32ed75514 100644 --- a/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html +++ b/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html @@ -1,5 +1,5 @@ <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> - <div class="widget-content-title"><span>{title}</span></div> + <div class="widget-content-title"><span>{f:translate(id: configuration.title, default: configuration.title)}</span></div> <div class="widget-content-main"> <f:render section="main" optional="true" /> </div> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html b/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html index 27c80a43bcac625fe88580bfa99aa68cc11503fb..b5f2ed8e133fb76a8d1f2389331fddc51058fb18 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html @@ -27,7 +27,7 @@ <f:then> <div class="dashboard-grid"> <f:for each="{currentDashboard.widgets}" as="widget" iteration="widgetIterator" key="widgetHash"> - <div class="dashboard-item dashboard-item--w{widget.width} dashboard-item--h{widget.height} {widget.additionalClasses} dashboard-item--enableSelect" data-widget-hash="{widgetHash}" data-widget-key="{widget.identifier}"> + <div class="dashboard-item dashboard-item--w-{widget.width} dashboard-item--h-{widget.height} {widget.additionalCssClasses} dashboard-item--enableSelect" data-widget-hash="{widgetHash}" data-widget-key="{widget.identifier}"> <div class="dashboard-item-content"> <div class="widget widget-identifier-{widget.identifier}"> <div class="widget-waiting"> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html index 4e753d334fe82f10c5a8030ba7699174d06b2d1b..45b2fbe4d322b6d1aee742a4fc5cbb3b3844a101 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html @@ -10,7 +10,7 @@ <f:section name="footer"> <f:if condition="{button}"> - <a href="{button.link}" target="{button.target}" class="widget-cta">{button.text}</a> + <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a> </f:if> </f:section> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html index 5f86147458f4bcce69d7ae8a418eed4be96b5d4e..97f590fa0c05b09e9845182eaacbe67d352bd6cc 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html @@ -3,24 +3,13 @@ <f:section name="main"> <f:if condition="{text}"> - {text} + <f:translate id='{text}' default='{text}' /> </f:if> </f:section> <f:section name="footer"> - - <f:if condition="{link}"> - <f:link.external uri="{link}" target="_blank" class="widget-cta"> - <f:if condition="{icon}"> - <div class="widget-cta-icon"> - <core:icon identifier="{icon}" size="large" alternativeMarkupIdentifier="inline" /> - </div> - </f:if> - <f:if condition="{label}"> - <f:translate key="{label}" default="{label}"/> - </f:if> - </f:link.external> + <f:if condition="{button}"> + <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a> </f:if> - </f:section> </html> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html index bbbac6ee9667fac4db6196d31dae4206052dae25..3e04b449ada86475b93d1157fe397b398316bc0a 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html @@ -5,7 +5,7 @@ <f:if condition="{items}"> <ul> <f:for each="{items}" as="item"> - <li><f:link.typolink parameter="{item.link}">{item.title}</f:link.typolink></li> + <li>{item}</li> </f:for> </ul> </f:if> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html index 99c210653e29bfb89920698f0448f07ad9515107..361eb0ba58ca322f469e872eb6310af4f3fd8793 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html @@ -9,9 +9,9 @@ </div> </f:if> <div class="widget-number-content"> - <div class="widget-number-title">{title}</div> + <div class="widget-number-title"><f:translate key="{title}" default="{title}" /></div> <f:if condition="{subtitle}"> - <div class="widget-number-subtitle"><small>{subtitle}</small></div> + <div class="widget-number-subtitle"><small><f:translate key="{subtitle}" default="{subtitle}" /></small></div> </f:if> <div class="widget-number-number">{number}</div> </div> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html index 5dd79fb931a1735caafc7f0b8c702ebad1113d37..2a261352455e4fcee21ae9212d78508b6ec887ff 100644 --- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html +++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html @@ -18,12 +18,8 @@ </f:section> <f:section name="footer"> - - <f:if condition="{moreItemsLink}"> - <f:link.typolink parameter="{moreItemsLink}" target="_blank" class="widget-cta"> - <f:translate key="{moreItemsText}" default="{moreItemsText}"/> - </f:link.typolink> + <f:if condition="{button}"> + <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a> </f:if> - </f:section> </html> diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformation.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformationWidget.html similarity index 100% rename from typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformation.html rename to typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformationWidget.html diff --git a/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css b/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css index 8d79f154f43cbbe67ac848babf7c72a9f30c8e6c..df55e386901ba1a0a78a4a5fb3af8bd5af6d02fe 100644 --- a/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css +++ b/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h4{height:400px}}@media screen and (min-width:750px){.dashboard-item--h6{height:600px}}.dashboard-item--w4{width:100%}@media screen and (min-width:1285px){.dashboard-item--w4{width:50%}}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.widget-number{height:100%;display:flex;align-items:center}.widget-number-icon{display:flex;align-items:center;width:42px;margin-right:20px;color:#000}.widget-number-content{display:flex;flex-direction:column;justify-content:center}.widget-number-title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.widget-number-number{line-height:1.3;font-weight:900;font-size:24px} \ No newline at end of file +.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h-medium{height:400px}}@media screen and (min-width:750px){.dashboard-item--h-large{height:600px}}.dashboard-item--w-medium{width:100%}@media screen and (min-width:1285px){.dashboard-item--w-medium{width:50%}}.dashboard-item--w-large{width:100%}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.widget-number{height:100%;display:flex;align-items:center}.widget-number-icon{display:flex;align-items:center;width:42px;margin-right:20px;color:#000}.widget-number-content{display:flex;flex-direction:column;justify-content:center}.widget-number-title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.widget-number-number{line-height:1.3;font-weight:900;font-size:24px} \ No newline at end of file diff --git a/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php b/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php new file mode 100644 index 0000000000000000000000000000000000000000..13b4176a900dcd842f67b95ab26b214054730cb2 --- /dev/null +++ b/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php @@ -0,0 +1,226 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Dashboard\Tests\Unit; + +/* + * 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 Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use TYPO3\CMS\Dashboard\DependencyInjection\DashboardWidgetPass; +use TYPO3\CMS\Dashboard\WidgetRegistry; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +class DashboardWidgetPassTest extends UnitTestCase +{ + /** + * @var DashboardWidgetPass + */ + protected $subject; + + /** + * @var ContainerBuilder + */ + protected $container; + + /** + * @var Definition + */ + protected $widgetRegistryDefinition; + + public function setUp(): void + { + parent::setUp(); + + $this->subject = new DashboardWidgetPass('dashboard.widget'); + $this->container = $this->prophesize(ContainerBuilder::class); + $this->widgetRegistryDefinition = $this->prophesize(Definition::class); + } + + /** + * @test + */ + public function doesNothingIfWidgetRegistryIsUnkown(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn(false); + $this->container->findTaggedServiceIds('dashboard.widget')->shouldNotBeCalled(); + + $this->subject->process($this->container->reveal()); + } + + /** + * @test + */ + public function doesNothingIfNoWidgetsAreTagged(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal()); + $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([])->shouldBeCalled(); + $this->widgetRegistryDefinition->addMethodCall()->shouldNotBeCalled(); + + $this->subject->process($this->container->reveal()); + } + + /** + * @test + */ + public function makesWidgetPublic(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal()); + $this->container->findTaggedServiceIds('dashboard.widget')->willReturn(['NewsWidget' => []]); + $definition = $this->prophesize(Definition::class); + $this->container->findDefinition('NewsWidget')->willReturn($definition->reveal()); + $definition->setPublic(true)->shouldBeCalled(); + + $this->subject->process($this->container->reveal()); + } + + /** + * @test + */ + public function registersTaggedWidgetWithMiniumConfigurationInRegistry(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal()); + $definition = $this->prophesize(Definition::class); + $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal()); + $definition->setPublic(true); + $definition->setArgument('$configuration', Argument::that(function ($argument) { + return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration'; + })); + + $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([ + 'dashboard.widget.t3news' => [ + [ + 'identifier' => 't3news', + 'groupNames' => 'typo3', + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description', + ] + ] + ]); + $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) { + $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration']; + /* @var Definition $definition */ + return $definition instanceof Definition + && $definition->getClass(WidgetConfiguration::class) + && $definition->getArgument('$identifier') === 't3news' + && $definition->getArgument('$groupNames') === ['typo3'] + && $definition->getArgument('$title') === 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title' + && $definition->getArgument('$description') === 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description' + && $definition->getArgument('$iconIdentifier') === 'content-dashboard' + && $definition->getArgument('$height') === 'small' + && $definition->getArgument('$width') === 'small' + ; + }))->shouldBeCalled(); + $this->widgetRegistryDefinition->addMethodCall( + 'registerWidget', + [ + 't3newsWidgetConfiguration', + ] + )->shouldBeCalled(); + + $this->subject->process($this->container->reveal()); + } + + /** + * @test + */ + public function registersWidgetToMultipleGroupsByComma(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal()); + $definition = $this->prophesize(Definition::class); + $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal()); + $definition->setPublic(true); + $definition->setArgument('$configuration', Argument::that(function ($argument) { + return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration'; + })); + + $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([ + 'dashboard.widget.t3news' => [ + [ + 'identifier' => 't3news', + 'groupNames' => 'typo3, general', + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description', + ] + ] + ]); + $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) { + $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration']; + /* @var Definition $definition */ + return $definition instanceof Definition + && $definition->getClass(WidgetConfiguration::class) + && $definition->getArgument('$groupNames') === ['typo3', 'general'] + ; + }))->shouldBeCalled(); + $this->widgetRegistryDefinition->addMethodCall( + 'registerWidget', + [ + 't3newsWidgetConfiguration', + ] + )->shouldBeCalled(); + + $this->subject->process($this->container->reveal()); + } + + /** + * @test + */ + public function registersTaggedWidgetWithMaximumConfigurationInRegistry(): void + { + $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal()); + $definition = $this->prophesize(Definition::class); + $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal()); + $definition->setPublic(true); + $definition->setArgument('$configuration', Argument::that(function ($argument) { + return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration'; + })); + + $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([ + 'dashboard.widget.t3news' => [ + [ + 'identifier' => 't3news', + 'groupNames' => 'typo3', + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description', + 'iconIdentifier' => 'some-icon', + 'height' => 'large', + 'width' => 'medium', + ] + ] + ]); + $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) { + $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration']; + /* @var Definition $definition */ + return $definition instanceof Definition + && $definition->getClass(WidgetConfiguration::class) + && $definition->getArgument('$identifier') === 't3news' + && $definition->getArgument('$groupNames') === ['typo3'] + && $definition->getArgument('$title') === 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title' + && $definition->getArgument('$description') === 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description' + && $definition->getArgument('$iconIdentifier') === 'some-icon' + && $definition->getArgument('$height') === 'large' + && $definition->getArgument('$width') === 'medium' + ; + }))->shouldBeCalled(); + $this->widgetRegistryDefinition->addMethodCall( + 'registerWidget', + [ + 't3newsWidgetConfiguration', + ] + )->shouldBeCalled(); + + $this->subject->process($this->container->reveal()); + } +} diff --git a/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php b/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php index ae2514e29f6c5e158b8d9133a200f263e4c4b0bc..7b245d443addb849f9d634453d538eced1797787 100644 --- a/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php +++ b/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php @@ -16,10 +16,12 @@ namespace TYPO3\CMS\Dashboard\Tests\Unit; * The TYPO3 project - inspiring people to share! */ +use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Psr\Container\ContainerInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Dashboard\WidgetRegistry; +use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class WidgetRegistryTest extends UnitTestCase @@ -29,7 +31,9 @@ class WidgetRegistryTest extends UnitTestCase */ protected $resetSingletonInstances = true; - /** @var WidgetRegistry */ + /** + * @var WidgetRegistry + */ protected $subject; /** @@ -37,13 +41,18 @@ class WidgetRegistryTest extends UnitTestCase */ protected $beUserProphecy; + /** + * @var ContainerInterface|ObjectProphecy + */ + protected $containerProphecy; + public function setUp(): void { $this->beUserProphecy = $this->prophesize(BackendUserAuthentication::class); - $containerProphecy = $this->prophesize(ContainerInterface::class); + $this->containerProphecy = $this->prophesize(ContainerInterface::class); $GLOBALS['BE_USER'] = $this->beUserProphecy->reveal(); - $this->subject = new WidgetRegistry($containerProphecy->reveal()); + $this->subject = new WidgetRegistry($this->containerProphecy->reveal()); } /** @@ -63,13 +72,31 @@ class WidgetRegistryTest extends UnitTestCase */ public function getAllWidgetReturnsAllRegisteredWidgets(array $expectedValues, array $widgetsToRegister): void { - foreach ($widgetsToRegister as $widget) { - $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']); - } + $this->registerWidgets($widgetsToRegister); self::assertCount((int)$expectedValues['count'], $this->subject->getAllWidgets()); } + /** + * @param array $expectedValues + * @param array $widgetsToRegister + * + * @test + * @dataProvider widgetsToRegister + */ + public function returnsWidgetsForGroup( + array $expectedValues, + array $widgetsToRegister + ): void { + $this->registerWidgets($widgetsToRegister); + $this->beUserProphecy->check('available_widgets', Argument::any())->willReturn(true); + + self::assertCount( + (int)$expectedValues['group1Count'], + $this->subject->getAvailableWidgetsForWidgetGroup('group1') + ); + } + /** * @param array $expectedValues * @param array $widgetsToRegister @@ -82,7 +109,7 @@ class WidgetRegistryTest extends UnitTestCase array $widgetsToRegister ): void { foreach ($widgetsToRegister as $widget) { - $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']); + $this->registerWidget($widget); $this->beUserProphecy ->check( @@ -107,7 +134,7 @@ class WidgetRegistryTest extends UnitTestCase array $widgetsToRegister ): void { foreach ($widgetsToRegister as $widget) { - $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']); + $this->registerWidget($widget); $this->beUserProphecy ->check( @@ -121,89 +148,242 @@ class WidgetRegistryTest extends UnitTestCase self::assertCount((int)$expectedValues['userCount'], $this->subject->getAvailableWidgets()); } + /** + * @test + */ + public function addWidgetsInItemsProcFunc(): void + { + $this->registerWidgets([ + [ + 'serviceName' => 'dashboard.widget.t3news', + 'identifier' => 't3orgnews', + 'groups' => ['typo3'], + 'iconIdentifier' => 'content-widget-rss', + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'height' => 4, + 'width' => 4, + 'additionalCssClasses' => [], + ], + [ + 'serviceName' => 'dashboard.widget.t3comnews', + 'identifier' => '2ndWidget', + 'groups' => ['typo3'], + 'iconIdentifier' => 'content-widget-2nd', + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.description', + 'height' => 4, + 'width' => 4, + 'additionalCssClasses' => [], + ], + ]); + + $parameters = []; + $this->subject->widgetItemsProcFunc($parameters); + + self::assertEquals( + [ + 'items' => [ + [ + 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 't3orgnews', + 'content-widget-rss', + 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + ], + [ + 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.title', + '2ndWidget', + 'content-widget-2nd', + 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.description', + ], + ] + ], + $parameters + ); + } + + private function registerWidgets(array $widgetsToRegister): void + { + foreach ($widgetsToRegister as $widget) { + $this->registerWidget($widget); + } + } + + private function registerWidget(array $widget) + { + $widgetConfiguration = $this->prophesize(WidgetConfigurationInterface::class); + $widgetConfiguration->getTitle()->willReturn($widget['title']); + $widgetConfiguration->getIdentifier()->willReturn($widget['identifier']); + $widgetConfiguration->getIconIdentifier()->willReturn($widget['iconIdentifier']); + $widgetConfiguration->getDescription()->willReturn($widget['description']); + $widgetConfiguration->getGroupNames()->willReturn($widget['groups']); + $widgetConfiguration->getHeight()->willReturn($widget['height']); + $widgetConfiguration->getWidth()->willReturn($widget['width']); + $widgetConfiguration->getAdditionalCssClasses()->willReturn($widget['additionalCssClasses']); + + $this->containerProphecy->get($widget['serviceName'])->willReturn($widgetConfiguration->reveal()); + $this->subject->registerWidget($widget['serviceName']); + } + public function widgetsToRegister(): array { return [ - [ + 'Single widget' => [ [ 'count' => 1, 'adminCount' => 1, - 'userCount' => 1 + 'userCount' => 1, + 'group1Count' => 1, ], [ [ 'identifier' => 'test-widget1', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3news', 'groups' => ['group1'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => true ] ] ], - [ + 'Two widgets' => [ [ 'count' => 2, 'adminCount' => 2, - 'userCount' => 1 + 'userCount' => 1, + 'group1Count' => 2, ], [ [ 'identifier' => 'test-widget1', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3news', 'groups' => ['group1'], - 'availableForUser' => true + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], + 'availableForUser' => true, ], [ 'identifier' => 'test-widget2', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3comnews', 'groups' => ['group1'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => false ], ] ], - [ + 'Three widgets, two having same identifier' => [ [ 'count' => 2, 'adminCount' => 2, - 'userCount' => 2 + 'userCount' => 2, + 'group1Count' => 1, ], [ [ 'identifier' => 'test-widget1', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3news', 'groups' => ['group1'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => true ], [ 'identifier' => 'test-widget1', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3news', 'groups' => ['group1'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => true ], [ 'identifier' => 'test-widget2', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3orgnews', 'groups' => ['group2'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => true ], ] ], - [ + 'Two widgets, one not available for user' => [ [ 'count' => 2, 'adminCount' => 2, - 'userCount' => 1 + 'userCount' => 1, + 'group1Count' => 1, ], [ [ 'identifier' => 'test-widget1', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', + 'serviceName' => 'dashboard.widget.t3news', 'groups' => ['group1'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => true ], [ 'identifier' => 'test-widget2', - 'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget', - 'groups' => ['group1', 'group2'], + 'serviceName' => 'dashboard.widget.t3comnews', + 'groups' => ['group2'], + 'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title', + 'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description', + 'iconIdentifier' => 'content-widget-rss', + 'height' => 2, + 'width' => 4, + 'additionalCssClasses' => [ + 'custom-widget', + 'rss-condensed', + ], 'availableForUser' => false ], ] diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php index 8afba7fb77eeb6420878ce2f786d6dcb73505f4a..42d542fafbfaf96cff47c50b9cbcbe3042f2027d 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php @@ -1412,4 +1412,69 @@ return [ 'Deprecation-90937-VariousHooksInContentObjectRenderer.rst', ], ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractChartWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractCtaButtonWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractListWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractNumberWithIconWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractRssWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\AbstractWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\DocumentationGettingStartedWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\DocumentationTSconfigReferenceWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\T3GeneralInformation' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], + 'TYPO3\CMS\Dashboard\Widgets\T3SecurityAdvisoriesWidget' => [ + 'restFiles' => [ + 'Breaking-90660-RegistrationOfWidgetsChanged.rst', + ], + ], ];