TYPO3 v15 dev-main snapshot ()
@@ -0,0 +1 @@
|
||||
/vendor/
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Dto\Settings\EditableSetting;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Settings\Category;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsDiff;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeRegistry;
|
||||
use TYPO3\CMS\Dashboard\DashboardPreset;
|
||||
use TYPO3\CMS\Dashboard\DashboardPresetRegistry;
|
||||
use TYPO3\CMS\Dashboard\Factory\WidgetSettingsFactory;
|
||||
use TYPO3\CMS\Dashboard\Repository\DashboardRepository;
|
||||
use TYPO3\CMS\Dashboard\WidgetGroupInitializationService;
|
||||
use TYPO3\CMS\Dashboard\WidgetRegistry;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class DashboardAjaxController
|
||||
{
|
||||
public function __construct(
|
||||
protected DashboardRepository $dashboardRepository,
|
||||
protected DashboardPresetRegistry $dashboardPresetRegistry,
|
||||
protected WidgetRegistry $widgetRegistry,
|
||||
protected WidgetGroupInitializationService $widgetGroupInitializationService,
|
||||
protected WidgetSettingsFactory $widgetSettingsFactory,
|
||||
protected SettingsTypeRegistry $settingsTypeRegistry,
|
||||
protected UriBuilder $uriBuilder,
|
||||
) {}
|
||||
|
||||
public function getDashboards(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboards = [];
|
||||
foreach ($availableDashboards as $dashboard) {
|
||||
$dashboard->initializeWidgets($request);
|
||||
$dashboards[] = $dashboard->getTransferData();
|
||||
}
|
||||
|
||||
return new JsonResponse($dashboards);
|
||||
}
|
||||
|
||||
public function addDashboard(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$presetIdentifier = (string)($request->getParsedBody()['preset'] ?? '');
|
||||
$dashboardPreset = $this->dashboardPresetRegistry->getDashboardPresets()[$presetIdentifier] ?? null;
|
||||
if (!$dashboardPreset instanceof DashboardPreset) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid dashboard preset!',
|
||||
]);
|
||||
}
|
||||
|
||||
$dashboardEntity = $this->dashboardRepository->create(
|
||||
$dashboardPreset,
|
||||
(int)$this->getBackendUser()->user['uid'],
|
||||
(string)($request->getParsedBody()['title'] ?? '')
|
||||
);
|
||||
|
||||
$dashboardEntity->initializeWidgets($request);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'dashboard' => $dashboardEntity->getTransferData(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function editDashboard(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$dashboardIdentifier = (string)($request->getParsedBody()['identifier'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboardEntity = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
|
||||
if (!in_array($dashboardEntity, $availableDashboards)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dashboardRepository->updateDashboardSettings(
|
||||
$dashboardIdentifier,
|
||||
[
|
||||
'title' => (string)($request->getParsedBody()['title'] ?? ''),
|
||||
]
|
||||
);
|
||||
|
||||
// Fetch updated Dashboard
|
||||
$dashboardEntity = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
$dashboardEntity->initializeWidgets($request);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'dashboard' => $dashboardEntity->getTransferData(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateDashboard(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$dashboardIdentifier = (string)($request->getParsedBody()['identifier'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboardEntity = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
|
||||
if (!in_array($dashboardEntity, $availableDashboards)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
$widgets = $request->getParsedBody()['widgets'] ?? [];
|
||||
$data = [];
|
||||
foreach ($widgets as $widget) {
|
||||
$data[$widget['identifier']] = [
|
||||
'identifier' => $widget['type'],
|
||||
];
|
||||
}
|
||||
|
||||
// positions
|
||||
$widgetPositions = $request->getParsedBody()['widgetPositions'] ?? [];
|
||||
foreach ($widgetPositions as $columnCount => $widgets) {
|
||||
foreach ($widgets as $widget) {
|
||||
if (!isset($widget['identifier']) || !isset($widget['height']) || !isset($widget['width']) || !isset($widget['x']) || !isset($widget['y'])) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid widget positions!',
|
||||
]);
|
||||
}
|
||||
$identifier = $widget['identifier'] ?? '';
|
||||
unset($widget['identifier']);
|
||||
$data[$identifier]['positions'][$columnCount] = array_map('intval', $widget);
|
||||
}
|
||||
}
|
||||
|
||||
// settings
|
||||
$dashboardEntity->initializeWidgets($request);
|
||||
foreach ($widgets as $widget) {
|
||||
$dashboardWidget = $dashboardEntity->getWidget($widget['identifier']);
|
||||
if ($dashboardWidget) {
|
||||
$data[$widget['identifier']]['settings'] = $dashboardWidget->getRawConfig()['settings'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
$this->dashboardRepository->updateWidgetConfig($dashboardEntity, $data);
|
||||
|
||||
// Fetch updated Dashboard
|
||||
$dashboardEntity = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
$dashboardEntity->initializeWidgets($request);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'dashboard' => $dashboardEntity->getTransferData(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteDashboard(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$dashboardIdentifier = (string)($request->getParsedBody()['identifier'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboard = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
|
||||
if (!in_array($dashboard, $availableDashboards)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dashboardRepository->delete($dashboard);
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPresets(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$presets = $this->dashboardPresetRegistry->getDashboardPresets();
|
||||
return new JsonResponse($presets);
|
||||
}
|
||||
|
||||
public function getCategories(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$widgetGroups = $this->widgetGroupInitializationService->buildWidgetGroupsConfiguration();
|
||||
return new JsonResponse($widgetGroups);
|
||||
}
|
||||
|
||||
public function getWidget(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$widgetIdentifier = (string)($request->getQueryParams()['widget'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$widgets = [];
|
||||
|
||||
foreach ($availableDashboards as $dashboard) {
|
||||
$dashboard->initializeWidgets($request);
|
||||
foreach ($dashboard->getWidgets() as $dashboardEntry) {
|
||||
$widgets[$dashboardEntry->getIdentifier()] = $dashboardEntry;
|
||||
}
|
||||
}
|
||||
|
||||
$dashboardWidget = $widgets[$widgetIdentifier] ?? null;
|
||||
if ($dashboardWidget === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget does not exist!',
|
||||
]);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'widget' => $dashboardWidget->getTransferWidgetData()->jsonSerialize(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getWidgetSettings(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$widgetIdentifier = (string)($request->getQueryParams()['widget'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$widgets = [];
|
||||
|
||||
foreach ($availableDashboards as $dashboard) {
|
||||
$dashboard->initializeWidgets($request);
|
||||
foreach ($dashboard->getWidgets() as $dashboardEntry) {
|
||||
$widgets[$dashboardEntry->getIdentifier()] = $dashboardEntry;
|
||||
}
|
||||
}
|
||||
|
||||
$dashboardWidget = $widgets[$widgetIdentifier] ?? null;
|
||||
if ($dashboardWidget === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget does not exist!',
|
||||
]);
|
||||
}
|
||||
|
||||
$categories = [
|
||||
new Category(
|
||||
key: $dashboardWidget->getType(),
|
||||
label: $this->getLanguageService()->sL($dashboardWidget->getTitle()),
|
||||
description: $this->getLanguageService()->sL($dashboardWidget->getDescription()),
|
||||
icon: $dashboardWidget->getIconIdentifier(),
|
||||
settings: array_map(
|
||||
fn(SettingDefinition $definition): EditableSetting => new EditableSetting(
|
||||
definition: $this->resolveSettingLabels($definition),
|
||||
value: $dashboardWidget->getSettings()->get($definition->key),
|
||||
systemDefault: $definition->default,
|
||||
typeImplementation: $this->settingsTypeRegistry->get($definition->type)->getJavaScriptModule(),
|
||||
),
|
||||
array_values(array_filter($dashboardWidget->getSettingsDefinitions(), fn(SettingDefinition $settingDefinition) => !$settingDefinition->readonly))
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'categories' => json_encode($categories),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateWidgetSettings(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// Check if identifier is available
|
||||
$widgetIdentifier = trim((string)($request->getParsedBody()['widget'] ?? ''));
|
||||
if ($widgetIdentifier === '') {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
// Check for widget
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$targetDashboard = null;
|
||||
$targetWidget = null;
|
||||
foreach ($availableDashboards as $dashboard) {
|
||||
$dashboard->initializeWidgets($request);
|
||||
if ($dashboard->getWidget($widgetIdentifier)) {
|
||||
$targetDashboard = $dashboard;
|
||||
$targetWidget = $dashboard->getWidget($widgetIdentifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetWidget === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget does not exist!',
|
||||
]);
|
||||
}
|
||||
if ($targetDashboard === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard does not exist!',
|
||||
]);
|
||||
}
|
||||
|
||||
$rawSettings = $request->getParsedBody()['settings'] ?? [];
|
||||
$widgetData = [];
|
||||
foreach ($targetDashboard->getWidgets() as $widget) {
|
||||
$widgetData[$widget->getIdentifier()] = $widget->getRawConfig();
|
||||
if ($targetWidget->getIdentifier() === $widget->getIdentifier()) {
|
||||
|
||||
$currentSettings = $widget->getRawConfig()['settings'] ?? [];
|
||||
$newSettings = $this->widgetSettingsFactory->createSettingsFromFormData($rawSettings, $widget->getSettingsDefinitions());
|
||||
$defaultSettings = $this->widgetSettingsFactory->createSettings($widget->getType(), [], $widget->getSettingsDefinitions());
|
||||
$diff = SettingsDiff::create(
|
||||
$currentSettings,
|
||||
$newSettings,
|
||||
$defaultSettings,
|
||||
);
|
||||
if ($diff->changes === [] && $diff->deletions === []) {
|
||||
return new JsonResponse([
|
||||
'status' => 'info',
|
||||
'message' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget.settings.unchanged'),
|
||||
]);
|
||||
}
|
||||
$widgetData[$widget->getIdentifier()]['settings'] = $diff->settings;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dashboardRepository->updateWidgetConfig($targetDashboard, $widgetData);
|
||||
|
||||
// Fetch updated Dashboard
|
||||
$dashboardEntity = $this->dashboardRepository->getDashboardByIdentifier($targetDashboard->getIdentifier());
|
||||
$dashboardEntity->initializeWidgets($request);
|
||||
|
||||
$returnWidget = $dashboardEntity->getWidget($targetWidget->getIdentifier());
|
||||
if ($returnWidget === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget does not exist!',
|
||||
]);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function addWidget(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$dashboardIdentifier = (string)($request->getParsedBody()['dashboard'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboard = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
|
||||
if (!in_array($dashboard, $availableDashboards)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
$widgetType = (string)($request->getParsedBody()['type'] ?? '');
|
||||
if ($widgetType === '') {
|
||||
throw new \InvalidArgumentException('Argument "widget" not set.', 1714987384);
|
||||
}
|
||||
$widgets = $dashboard->getWidgetConfig();
|
||||
$widgetIdentifier = sha1($widgetType . '-' . time());
|
||||
$widgets[$widgetIdentifier] = ['identifier' => $widgetType];
|
||||
$this->dashboardRepository->updateWidgetConfig($dashboard, $widgets);
|
||||
|
||||
// Fetch updated Dashboard
|
||||
$dashboard = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
$dashboard->initializeWidgets($request);
|
||||
|
||||
$widgets = [];
|
||||
foreach ($dashboard->getWidgets() as $dashboardEntry) {
|
||||
$widgets[$dashboardEntry->getIdentifier()] = $dashboardEntry;
|
||||
}
|
||||
|
||||
$dashboardWidget = $widgets[$widgetIdentifier] ?? null;
|
||||
if ($dashboardWidget === null) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'widget' => $dashboardWidget->getTransferWidgetData()->jsonSerialize(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function removeWidget(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$dashboardIdentifier = (string)($request->getParsedBody()['dashboard'] ?? '');
|
||||
$availableDashboards = $this->dashboardRepository->getDashboardsForUser($this->getBackendUser()->getUserId());
|
||||
$dashboard = $this->dashboardRepository->getDashboardByIdentifier($dashboardIdentifier);
|
||||
|
||||
if (!in_array($dashboard, $availableDashboards)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Dashboard is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
$widgetIdentifier = (string)($request->getParsedBody()['identifier'] ?? '');
|
||||
$widgets = $dashboard->getWidgetConfig();
|
||||
if ($widgetIdentifier === '' || !array_key_exists($widgetIdentifier, $widgets)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Widget is not available!',
|
||||
]);
|
||||
}
|
||||
|
||||
unset($widgets[$widgetIdentifier]);
|
||||
$this->dashboardRepository->updateWidgetConfig($dashboard, $widgets);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveSettingLabels(SettingDefinition $definition): SettingDefinition
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
return new SettingDefinition(...[
|
||||
...get_object_vars($definition),
|
||||
'label' => $languageService->sL($definition->label),
|
||||
'description' => $definition->description !== null ? $languageService->sL($definition->description) : null,
|
||||
'enum' => array_map(static fn(string $label): string => $languageService->sL($label), $definition->enum),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Dashboard\DashboardInitializationService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class DashboardController
|
||||
{
|
||||
public function __construct(
|
||||
protected PageRenderer $pageRenderer,
|
||||
protected DashboardInitializationService $dashboardInitializationService,
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
) {}
|
||||
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->dashboardInitializationService->initializeDashboards($request, $this->getBackendUser());
|
||||
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$this->preparePageRenderer();
|
||||
$this->addFrontendResources();
|
||||
$view->setTitle($this->getLanguageService()->translate('title', 'dashboard.module'));
|
||||
$view->getDocHeaderComponent()->disable();
|
||||
|
||||
return $view->renderResponse('Dashboard/Main');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds CSS and JS files that are necessary for widgets to the page renderer
|
||||
*/
|
||||
protected function addFrontendResources(): void
|
||||
{
|
||||
$javaScriptRenderer = $this->pageRenderer->getJavaScriptRenderer();
|
||||
foreach ($this->dashboardInitializationService->getJavaScriptModuleInstructions() as $instruction) {
|
||||
$javaScriptRenderer->addJavaScriptModuleInstruction($instruction);
|
||||
}
|
||||
foreach ($this->dashboardInitializationService->getCssFiles() as $cssFile) {
|
||||
$this->pageRenderer->addCssFile($cssFile);
|
||||
}
|
||||
foreach ($this->dashboardInitializationService->getJsFiles() as $jsFile) {
|
||||
$this->pageRenderer->addJsFile($jsFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the CSS and JS of the dashboard module to the page renderer
|
||||
*/
|
||||
protected function preparePageRenderer(): void
|
||||
{
|
||||
$this->pageRenderer->loadJavaScriptModule('@typo3/dashboard/dashboard.js');
|
||||
$this->pageRenderer->addCssFile('EXT:dashboard/Resources/Public/Css/dashboard.css');
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Settings\Settings;
|
||||
use TYPO3\CMS\Dashboard\Dto\Dashboard as TransferDashboard;
|
||||
use TYPO3\CMS\Dashboard\Dto\WidgetConfiguration as TransferWidgetConfiguration;
|
||||
use TYPO3\CMS\Dashboard\Factory\WidgetSettingsFactory;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetContext;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Dashboard
|
||||
{
|
||||
/**
|
||||
* @var array<string,DashboardEntry>
|
||||
*/
|
||||
protected array $widgets = [];
|
||||
|
||||
protected ?object $widgetPositions = null;
|
||||
|
||||
/**
|
||||
* @param array<string,array<string,string|array>> $widgetConfig
|
||||
*/
|
||||
public function __construct(
|
||||
protected readonly string $identifier,
|
||||
protected readonly string $title,
|
||||
protected readonly array $widgetConfig,
|
||||
protected readonly WidgetRegistry $widgetRegistry,
|
||||
protected readonly WidgetSettingsFactory $widgetSettingsFactory,
|
||||
protected readonly ContainerInterface $container,
|
||||
) {}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->getLanguageService()->sL($this->title) ?: $this->title;
|
||||
}
|
||||
|
||||
public function getWidgetConfig(): array
|
||||
{
|
||||
return $this->widgetConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,DashboardEntry>
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return $this->widgets;
|
||||
}
|
||||
|
||||
public function getWidget(string $identifier): ?DashboardEntry
|
||||
{
|
||||
return $this->widgets[$identifier] ?? null;
|
||||
}
|
||||
|
||||
public function getWidgetPositions(): object
|
||||
{
|
||||
return $this->widgetPositions ?? new \stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return a list of all widgets of the current dashboard object. It will only include available
|
||||
* widgets and will add the initialized object of the widget itself
|
||||
*/
|
||||
public function initializeWidgets(ServerRequestInterface $request): void
|
||||
{
|
||||
$availableWidgets = $this->widgetRegistry->getAvailableWidgets();
|
||||
$this->widgetPositions = new \stdClass();
|
||||
foreach ($this->widgetConfig as $hash => $widgetConfig) {
|
||||
$widgetConfigIdentifier = $widgetConfig['identifier'] ?? '';
|
||||
if ($widgetConfigIdentifier !== '' && array_key_exists($widgetConfigIdentifier, $availableWidgets)) {
|
||||
|
||||
// Widget (Renderer) Instance
|
||||
$widgetRenderer = $this->widgetRegistry->getAvailableWidget($request, $widgetConfigIdentifier);
|
||||
|
||||
// Dashboard Entry with Widget Context
|
||||
$this->widgets[$hash] = new DashboardEntry(
|
||||
context: new WidgetContext(
|
||||
identifier: $hash,
|
||||
rawData: $widgetConfig,
|
||||
configuration: $availableWidgets[$widgetConfigIdentifier],
|
||||
settings: $widgetRenderer instanceof WidgetRendererInterface ? $this->widgetSettingsFactory->createSettings(
|
||||
$widgetConfigIdentifier,
|
||||
$widgetConfig['settings'] ?? [],
|
||||
$widgetRenderer->getSettingsDefinitions(),
|
||||
) : new Settings([]),
|
||||
request: $request,
|
||||
),
|
||||
renderer: $widgetRenderer,
|
||||
);
|
||||
|
||||
// Widget Positions
|
||||
$positions = $widgetConfig['positions'] ?? [];
|
||||
foreach ($positions as $columnCount => $position) {
|
||||
if (!isset($position['height']) || !isset($position['width']) || !isset($position['x']) || !isset($position['y'])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($this->widgetPositions->{$columnCount})) {
|
||||
$this->widgetPositions->{$columnCount} = [];
|
||||
}
|
||||
$this->widgetPositions->{$columnCount}[] = [
|
||||
'identifier' => $hash,
|
||||
'height' => (int)$position['height'],
|
||||
'width' => (int)$position['width'],
|
||||
'x' => (int)$position['x'],
|
||||
'y' => (int)$position['y'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (array_keys(get_object_vars($this->widgetPositions)) as $columnCount) {
|
||||
usort(
|
||||
$this->widgetPositions->{$columnCount},
|
||||
static fn(array $a, array $b): int => $a['y'] !== $b['y'] ? $a['y'] - $b['y'] : $a['x'] - $b['x']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTransferData(): TransferDashboard
|
||||
{
|
||||
return new TransferDashboard(
|
||||
identifier: $this->getIdentifier(),
|
||||
title: $this->getTitle(),
|
||||
widgets: array_values(array_map(fn(DashboardEntry $entry): TransferWidgetConfiguration => $entry->getTransferWidgetConfiguration(), $this->getWidgets())),
|
||||
widgetPositions: $this->getWidgetPositions(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
use TYPO3\CMS\Dashboard\Dto\WidgetConfiguration as TransferWidgetConfiguration;
|
||||
use TYPO3\CMS\Dashboard\Dto\WidgetData as TransferWidgetData;
|
||||
use TYPO3\CMS\Dashboard\Widgets\EventDataInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetContext;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetResult;
|
||||
|
||||
/**
|
||||
* Dashboard entry representing a widget instance within a dashboard.
|
||||
*
|
||||
* This class encapsulates a dashboard widget instance, providing access to its context,
|
||||
* configuration, settings, and rendering capabilities. It serves as a bridge between
|
||||
* the dashboard system and individual widget implementations, handling both legacy
|
||||
* WidgetInterface and new WidgetRendererInterface widgets.
|
||||
*
|
||||
* Each dashboard entry maintains its own widget context with instance-specific settings
|
||||
* and provides methods for rendering and configuration management.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class DashboardEntry
|
||||
{
|
||||
public function __construct(
|
||||
private WidgetContext $context,
|
||||
private WidgetRendererInterface|WidgetInterface $renderer,
|
||||
) {}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->context->identifier;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->context->configuration->getIdentifier();
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->context->configuration->getTitle();
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->context->configuration->getDescription();
|
||||
}
|
||||
|
||||
public function getIconIdentifier(): string
|
||||
{
|
||||
return $this->context->configuration->getIconIdentifier();
|
||||
}
|
||||
|
||||
public function getHeight(): string
|
||||
{
|
||||
return $this->context->configuration->getHeight();
|
||||
}
|
||||
|
||||
public function getWidth(): string
|
||||
{
|
||||
return $this->context->configuration->getWidth();
|
||||
}
|
||||
|
||||
public function getSettings(): SettingsInterface
|
||||
{
|
||||
return $this->context->settings;
|
||||
}
|
||||
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->context->request;
|
||||
}
|
||||
|
||||
public function getEventData(): array
|
||||
{
|
||||
return ($this->renderer instanceof EventDataInterface) ? $this->renderer->getEventData() : [];
|
||||
}
|
||||
|
||||
public function getTransferWidgetConfiguration(): TransferWidgetConfiguration
|
||||
{
|
||||
return new TransferWidgetConfiguration(
|
||||
identifier: $this->getIdentifier(),
|
||||
type: $this->getType(),
|
||||
height: $this->getHeight(),
|
||||
width: $this->getWidth(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getTransferWidgetData(): TransferWidgetData
|
||||
{
|
||||
$result = $this->render();
|
||||
return new TransferWidgetData(
|
||||
identifier: $this->getIdentifier(),
|
||||
type: $this->getType(),
|
||||
height: $this->getHeight(),
|
||||
width: $this->getWidth(),
|
||||
label: $result->label ?? $this->getLanguageService()->sL($this->getTitle()),
|
||||
content: $result->content,
|
||||
eventdata: $this->getEventData(),
|
||||
refreshable: $result->refreshable,
|
||||
configurable: (
|
||||
$this->renderer instanceof WidgetRendererInterface
|
||||
&& array_filter($this->renderer->getSettingsDefinitions(), fn($definition) => !$definition->readonly) !== []
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
if ($this->renderer instanceof WidgetRendererInterface) {
|
||||
return $this->renderer->getSettingsDefinitions();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getRawConfig(): array
|
||||
{
|
||||
return $this->context->rawData;
|
||||
}
|
||||
|
||||
private function render(): WidgetResult
|
||||
{
|
||||
try {
|
||||
if ($this->renderer instanceof WidgetRendererInterface) {
|
||||
return $this->renderer->renderWidget($this->context);
|
||||
}
|
||||
// Map legacy WidgetInterface to "new" WidgetResult
|
||||
return new WidgetResult(
|
||||
content: $this->renderer->renderWidgetContent(),
|
||||
refreshable: $this->renderer->getOptions()['refreshAvailable'] ?? false,
|
||||
);
|
||||
|
||||
} catch (\Exception) {
|
||||
return new WidgetResult(
|
||||
content: sprintf('<div class="widget-content-main">%s</div>', $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget.error')),
|
||||
refreshable: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Dashboard\Repository\DashboardRepository;
|
||||
use TYPO3\CMS\Dashboard\Widgets\AdditionalCssInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\AdditionalJavaScriptInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DashboardInitializationService
|
||||
{
|
||||
protected const MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER = 'dashboard/current_dashboard/';
|
||||
|
||||
/**
|
||||
* @var list<JavaScriptModuleInstruction>
|
||||
*/
|
||||
protected array $javaScriptModuleInstructions = [];
|
||||
private array $jsFiles = [];
|
||||
private array $cssFiles = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly DashboardRepository $dashboardRepository,
|
||||
private readonly DashboardPresetRegistry $dashboardPresetRegistry,
|
||||
private readonly WidgetRegistry $widgetRegistry,
|
||||
) {}
|
||||
|
||||
public function initializeDashboards(ServerRequestInterface $request, BackendUserAuthentication $user): void
|
||||
{
|
||||
$this->defineCurrentDashboard($user);
|
||||
$this->defineResourcesOfWidgets();
|
||||
}
|
||||
|
||||
protected function defineCurrentDashboard(BackendUserAuthentication $user): Dashboard
|
||||
{
|
||||
$currentDashboard = $this->dashboardRepository->getDashboardByIdentifier($this->loadCurrentDashboard($user));
|
||||
if ($currentDashboard === null) {
|
||||
$dashboards = $this->getDashboardsForUser($user);
|
||||
/** @var Dashboard $currentDashboard */
|
||||
$currentDashboard = reset($dashboards);
|
||||
$this->saveCurrentDashboard($user, $currentDashboard->getIdentifier());
|
||||
}
|
||||
|
||||
return $currentDashboard;
|
||||
}
|
||||
|
||||
protected function createDefaultDashboards(BackendUserAuthentication $user): array
|
||||
{
|
||||
$dashboardsForUser = [];
|
||||
|
||||
$userConfig = $user->getTSConfig();
|
||||
$dashboardsToCreate = GeneralUtility::trimExplode(
|
||||
',',
|
||||
$userConfig['options.']['dashboard.']['dashboardPresetsForNewUsers'] ?? 'default'
|
||||
);
|
||||
|
||||
foreach ($this->dashboardPresetRegistry->getDashboardPresets() as $dashboardPreset) {
|
||||
if (in_array($dashboardPreset->getIdentifier(), $dashboardsToCreate, true)) {
|
||||
$dashboard = $this->dashboardRepository->create(
|
||||
$dashboardPreset,
|
||||
(int)$user->user['uid']
|
||||
);
|
||||
|
||||
if ($dashboard === null) {
|
||||
continue;
|
||||
}
|
||||
$dashboardsForUser[$dashboard->getIdentifier()] = $dashboard;
|
||||
}
|
||||
}
|
||||
|
||||
return $dashboardsForUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Dashboard[]
|
||||
*/
|
||||
public function getDashboardsForUser(BackendUserAuthentication $user): array
|
||||
{
|
||||
$dashboards = [];
|
||||
foreach ($this->dashboardRepository->getDashboardsForUser((int)$user->user['uid']) as $dashboard) {
|
||||
$dashboards[$dashboard->getIdentifier()] = $dashboard;
|
||||
}
|
||||
|
||||
if ($dashboards === []) {
|
||||
$dashboards = $this->createDefaultDashboards($user);
|
||||
}
|
||||
|
||||
return $dashboards;
|
||||
}
|
||||
|
||||
protected function defineResourcesOfWidgets(): void
|
||||
{
|
||||
foreach ($this->widgetRegistry->getAvailableWidgets() as $widget) {
|
||||
$concreteInstance = GeneralUtility::makeInstance($widget->getServiceName());
|
||||
if ($concreteInstance instanceof JavaScriptInterface) {
|
||||
$this->defineJavaScriptInstructions($concreteInstance);
|
||||
}
|
||||
if ($concreteInstance instanceof AdditionalCssInterface) {
|
||||
$this->defineCssFiles($concreteInstance);
|
||||
}
|
||||
if ($concreteInstance instanceof AdditionalJavaScriptInterface) {
|
||||
$this->defineJsFiles($concreteInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function defineJavaScriptInstructions(JavaScriptInterface $widgetInstance): void
|
||||
{
|
||||
foreach ($widgetInstance->getJavaScriptModuleInstructions() as $instruction) {
|
||||
$this->javaScriptModuleInstructions[] = $instruction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected function defineJsFiles(AdditionalJavaScriptInterface $widgetInstance): void
|
||||
{
|
||||
foreach ($widgetInstance->getJsFiles() as $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
|
||||
*/
|
||||
protected function defineCssFiles(AdditionalCssInterface $widgetInstance): void
|
||||
{
|
||||
foreach ($widgetInstance->getCssFiles() as $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 list<JavaScriptModuleInstruction>
|
||||
*/
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return $this->javaScriptModuleInstructions;
|
||||
}
|
||||
|
||||
public function getJsFiles(): array
|
||||
{
|
||||
return $this->jsFiles;
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return $this->cssFiles;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class DashboardPreset implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param list<array{identifier: string, settings?: array<string, mixed>}> $defaultWidgets
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $identifier,
|
||||
protected string $title,
|
||||
protected string $description,
|
||||
protected string $iconIdentifier = 'content-dashboard',
|
||||
protected array $defaultWidgets = [],
|
||||
protected bool $showInWizard = true
|
||||
) {}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getIconIdentifier(): string
|
||||
{
|
||||
return $this->iconIdentifier;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->getLanguageService()->sL($this->title) ?: $this->title;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->getLanguageService()->sL($this->description) ?: $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{identifier: string, settings?: array<string, mixed>}>
|
||||
*/
|
||||
public function getDefaultWidgets(): array
|
||||
{
|
||||
return $this->defaultWidgets;
|
||||
}
|
||||
|
||||
public function isShowInWizard(): bool
|
||||
{
|
||||
return $this->showInWizard;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->getIdentifier(),
|
||||
'title' => $this->getTitle(),
|
||||
'description' => $this->getDescription(),
|
||||
'icon' => $this->getIconIdentifier(),
|
||||
'widgets' => $this->getDefaultWidgets(),
|
||||
'showInWizard' => $this->isShowInWizard(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DashboardPresetRegistry
|
||||
{
|
||||
/**
|
||||
* @var DashboardPreset[]
|
||||
*/
|
||||
private array $dashboardPresets = [];
|
||||
|
||||
public function getDashboardPresets(): array
|
||||
{
|
||||
if (empty($this->dashboardPresets)) {
|
||||
$fallbackDashboardPreset = new DashboardPreset(
|
||||
'dashboardPreset-fallback',
|
||||
'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default',
|
||||
'',
|
||||
'content-dashboard',
|
||||
[],
|
||||
false
|
||||
);
|
||||
|
||||
return [
|
||||
'dashboardPreset-fallback' => $fallbackDashboardPreset,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->dashboardPresets;
|
||||
}
|
||||
|
||||
public function registerDashboardPreset(DashboardPreset $dashboardPreset): void
|
||||
{
|
||||
$this->dashboardPresets[$dashboardPreset->getIdentifier()] = $dashboardPreset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
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\AdminOnlyWidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class DashboardWidgetPass implements CompilerPassInterface
|
||||
{
|
||||
public function __construct(private string $tagName) {}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition(WidgetRegistry::class)) {
|
||||
return;
|
||||
}
|
||||
$widgetRegistryDefinition = $container->findDefinition(WidgetRegistry::class);
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
$definition = $container->findDefinition($serviceName);
|
||||
$definition->setPublic(true);
|
||||
|
||||
foreach ($tags as $attributes) {
|
||||
$identifier = $attributes['identifier'] ?? $serviceName;
|
||||
$attributes['identifier'] = $identifier;
|
||||
$attributes['serviceName'] = $serviceName;
|
||||
$attributes['adminOnly'] = is_a($definition->getClass(), AdminOnlyWidgetInterface::class, true);
|
||||
$attributes = $this->convertAttributes($attributes);
|
||||
|
||||
$configurationServiceName = $this->registerWidgetConfigurationService(
|
||||
$container,
|
||||
$identifier,
|
||||
$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'] = [];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Dto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class Dashboard implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
private string $title,
|
||||
private array $widgets,
|
||||
private object $widgetPositions,
|
||||
) {}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->identifier,
|
||||
'title' => $this->title,
|
||||
'widgets' => $this->widgets,
|
||||
'widgetPositions' => $this->widgetPositions,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Dto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class WidgetConfiguration implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
private string $type,
|
||||
private string $height,
|
||||
private string $width,
|
||||
) {}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->identifier,
|
||||
'type' => $this->type,
|
||||
'height' => $this->height,
|
||||
'width' => $this->width,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Dto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class WidgetData implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
private string $type,
|
||||
private string $height,
|
||||
private string $width,
|
||||
private string $label,
|
||||
private string $content,
|
||||
private array $eventdata,
|
||||
private bool $refreshable,
|
||||
private bool $configurable,
|
||||
) {}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->identifier,
|
||||
'type' => $this->type,
|
||||
'height' => $this->height,
|
||||
'width' => $this->width,
|
||||
'label' => $this->label,
|
||||
'content' => $this->content,
|
||||
'eventdata' => $this->eventdata,
|
||||
'refreshable' => $this->refreshable,
|
||||
'configurable' => $this->configurable,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Dto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class WidgetPosition implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
private int $height,
|
||||
private int $width,
|
||||
private int $y,
|
||||
private int $x,
|
||||
) {}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => $this->identifier,
|
||||
'height' => $this->height,
|
||||
'width' => $this->width,
|
||||
'y' => $this->y,
|
||||
'x' => $this->x,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\EventListener;
|
||||
|
||||
use TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
|
||||
/**
|
||||
* Adds custom CSS needed for EXT:dashboard in the "outer" backend scaffold
|
||||
*/
|
||||
final readonly class AfterBackendPageRenderEventListener
|
||||
{
|
||||
public function __construct(private PageRenderer $pageRenderer) {}
|
||||
|
||||
#[AsEventListener(event: AfterBackendPageRenderEvent::class)]
|
||||
public function __invoke(): void
|
||||
{
|
||||
$this->pageRenderer->addCssFile('EXT:dashboard/Resources/Public/Css/Modal/style.css');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
class InvalidRssFeedException extends Exception {}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinitionValidation;
|
||||
use TYPO3\CMS\Core\Settings\SettingsFactory;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsProvider;
|
||||
|
||||
/**
|
||||
* Factory for creating widget settings instances.
|
||||
*
|
||||
* This factory creates Settings objects for dashboard widgets, combining default
|
||||
* values from setting definitions with instance-specific values. It handles
|
||||
* validation of setting definitions and provides utilities for creating settings
|
||||
* from form data submitted through the widget configuration interface.
|
||||
*
|
||||
* The factory supports creating settings with various options:
|
||||
* - Respecting readonly settings
|
||||
* - Omitting default values when needed
|
||||
* - Processing form data for widget configuration updates
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class WidgetSettingsFactory
|
||||
{
|
||||
public function __construct(
|
||||
protected SettingsFactory $settingsFactory,
|
||||
protected SettingDefinitionValidation $settingDefinitionValidation,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param SettingDefinition[] $definitions
|
||||
*/
|
||||
public function createSettings(
|
||||
string $name,
|
||||
array $settings,
|
||||
array $definitions,
|
||||
bool $respectReadonly = false,
|
||||
bool $omitDefaults = false,
|
||||
): SettingsInterface {
|
||||
$defaultSettings = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$this->settingDefinitionValidation->validate($definition);
|
||||
if (!$omitDefaults) {
|
||||
$defaultSettings[$definition->key] = $definition->default;
|
||||
}
|
||||
}
|
||||
return $this->settingsFactory->resolveSettings(
|
||||
new SettingsProvider($name, $defaultSettings, $definitions),
|
||||
new SettingsProvider($name . ':instance', $settings, []),
|
||||
);
|
||||
}
|
||||
|
||||
public function createSettingsFromFormData(array $settings, array $definitions): SettingsInterface
|
||||
{
|
||||
return $this->settingsFactory->createSettingsFromFormData($settings, $definitions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Persistence;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Fill the "cruser_id" for new dashboards.
|
||||
*
|
||||
* @internal this is a TYPO3-internal specific hook implementation and not part of TYPO3's Public API
|
||||
*/
|
||||
class DashboardCreationEnricher
|
||||
{
|
||||
/**
|
||||
* @param array $incomingFieldArray
|
||||
* @param string $table
|
||||
* @param string $id
|
||||
*/
|
||||
public function processDatamap_preProcessFieldArray(&$incomingFieldArray, $table, $id, DataHandler $dataHandler)
|
||||
{
|
||||
// Not within be_dashboards
|
||||
if ($table !== 'be_dashboards') {
|
||||
return;
|
||||
}
|
||||
// Existing record, nothing to change
|
||||
if (MathUtility::canBeInterpretedAsInteger($id)) {
|
||||
return;
|
||||
}
|
||||
if (isset($incomingFieldArray['cruser_id'])) {
|
||||
return;
|
||||
}
|
||||
$incomingFieldArray['cruser_id'] = $dataHandler->BE_USER->user['uid'] ?? 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Repository;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Dashboard\Dashboard;
|
||||
use TYPO3\CMS\Dashboard\DashboardPreset;
|
||||
use TYPO3\CMS\Dashboard\Factory\WidgetSettingsFactory;
|
||||
use TYPO3\CMS\Dashboard\WidgetRegistry;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DashboardRepository
|
||||
{
|
||||
private const string TABLE = 'be_dashboards';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $allowedFields = ['title'];
|
||||
|
||||
/**
|
||||
* @var list<WidgetRendererInterface|WidgetInterface>
|
||||
*/
|
||||
protected array $widgets = [];
|
||||
|
||||
public function __construct(
|
||||
protected readonly ConnectionPool $connectionPool,
|
||||
protected readonly WidgetRegistry $widgetRegistry,
|
||||
protected readonly WidgetSettingsFactory $widgetSettingsFactory,
|
||||
protected readonly ContainerInterface $container
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return Dashboard[]
|
||||
*/
|
||||
public function getDashboardsForUser(int $userId): array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$rows = $queryBuilder
|
||||
->select('*')
|
||||
->from(self::TABLE)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('cruser_id', $queryBuilder->createNamedParameter($userId))
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
$results = [];
|
||||
foreach ($rows as $row) {
|
||||
$results[] = $this->createFromRow($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function create(DashboardPreset $dashboardPreset, int $userId, string $title = ''): ?Dashboard
|
||||
{
|
||||
$widgets = [];
|
||||
$title = $title ?: $dashboardPreset->getTitle();
|
||||
|
||||
foreach ($dashboardPreset->getDefaultWidgets() as $defaultWidget) {
|
||||
$hash = sha1(StringUtility::getUniqueId('widget_') . '-' . $defaultWidget['identifier']);
|
||||
$widgets[$hash] = $defaultWidget;
|
||||
}
|
||||
$identifier = sha1($dashboardPreset->getIdentifier() . '-' . time());
|
||||
$this->getQueryBuilder()
|
||||
->insert(self::TABLE)
|
||||
->values([
|
||||
'identifier' => $identifier,
|
||||
'title' => $title,
|
||||
'tstamp' => time(),
|
||||
'crdate' => time(),
|
||||
'cruser_id' => $userId,
|
||||
'widgets' => json_encode($widgets),
|
||||
])
|
||||
->executeStatement();
|
||||
return $this->getDashboardByIdentifier($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function updateDashboardSettings(string $identifier, array $values)
|
||||
{
|
||||
$checkedValues = $this->checkAllowedFields($values);
|
||||
|
||||
if (empty($checkedValues)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder->update(self::TABLE)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'identifier',
|
||||
$queryBuilder->createNamedParameter($identifier)
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($checkedValues as $field => $value) {
|
||||
$queryBuilder->set($field, $value);
|
||||
}
|
||||
|
||||
return $queryBuilder->executeStatement();
|
||||
}
|
||||
|
||||
protected function checkAllowedFields(array $values): array
|
||||
{
|
||||
$allowedFields = [];
|
||||
foreach ($values as $field => $value) {
|
||||
if (!empty($value) && in_array((string)$field, $this->allowedFields, true)) {
|
||||
$allowedFields[$field] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $allowedFields;
|
||||
}
|
||||
|
||||
public function getDashboardByIdentifier(string $identifier): ?Dashboard
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$row = $queryBuilder
|
||||
->select('*')
|
||||
->from(self::TABLE)
|
||||
->where($queryBuilder->expr()->eq('identifier', $queryBuilder->createNamedParameter($identifier)))
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
if (count($row)) {
|
||||
return $this->createFromRow($row[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array<string, mixed>> $widgets
|
||||
*/
|
||||
public function updateWidgetConfig(Dashboard $dashboard, array $widgets): void
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder
|
||||
->update(self::TABLE)
|
||||
->set('widgets', json_encode($widgets))
|
||||
->where($queryBuilder->expr()->eq('identifier', $queryBuilder->createNamedParameter($dashboard->getIdentifier())))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function delete(Dashboard $dashboard): void
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder
|
||||
->update(self::TABLE)
|
||||
->set('deleted', 1)
|
||||
->where($queryBuilder->expr()->eq('identifier', $queryBuilder->createNamedParameter($dashboard->getIdentifier())))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
protected function createFromRow(array $row): Dashboard
|
||||
{
|
||||
return GeneralUtility::makeInstance(
|
||||
Dashboard::class,
|
||||
$row['identifier'] ?? '',
|
||||
$row['title'] ?? '',
|
||||
json_decode((string)($row['widgets'] ?? ''), true) ?? [],
|
||||
$this->widgetRegistry,
|
||||
$this->widgetSettingsFactory,
|
||||
$this->container
|
||||
);
|
||||
}
|
||||
|
||||
protected function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return $this->connectionPool->getQueryBuilderForTable(self::TABLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
|
||||
use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
|
||||
use TYPO3\CMS\Core\Package\AbstractServiceProvider;
|
||||
use TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
private const string CACHE_IDENTIFIER_PREFIX = 'Dashboard_';
|
||||
|
||||
protected static function getPackagePath(): string
|
||||
{
|
||||
return __DIR__ . '/../';
|
||||
}
|
||||
|
||||
protected static function getPackageName(): string
|
||||
{
|
||||
return 'typo3/cms-dashboard';
|
||||
}
|
||||
|
||||
public function getFactories(): array
|
||||
{
|
||||
return [
|
||||
'dashboard.presets' => self::getDashboardPresets(...),
|
||||
'dashboard.widgetGroups' => self::getWidgetGroups(...),
|
||||
'dashboard.widgets' => self::getWidgets(...),
|
||||
'dashboard.configuration.warmer' => self::getConfigurationWarmer(...),
|
||||
];
|
||||
}
|
||||
|
||||
public function getExtensions(): array
|
||||
{
|
||||
return [
|
||||
DashboardPresetRegistry::class => self::configureDashboardPresetRegistry(...),
|
||||
ListenerProvider::class => self::addEventListeners(...),
|
||||
WidgetGroupRegistry::class => self::configureWidgetGroupRegistry(...),
|
||||
'dashboard.presets' => self::configureDashboardPresets(...),
|
||||
'dashboard.widgetGroups' => self::configureWidgetGroups(...),
|
||||
'dashboard.widgets' => self::configureWidgets(...),
|
||||
] + parent::getExtensions();
|
||||
}
|
||||
|
||||
public static function getDashboardPresets(ContainerInterface $container): \ArrayObject
|
||||
{
|
||||
return new \ArrayObject();
|
||||
}
|
||||
|
||||
public static function getWidgetGroups(ContainerInterface $container): \ArrayObject
|
||||
{
|
||||
return new \ArrayObject();
|
||||
}
|
||||
|
||||
public static function getWidgets(ContainerInterface $container): \ArrayObject
|
||||
{
|
||||
return new \ArrayObject();
|
||||
}
|
||||
|
||||
public static function configureDashboardPresetRegistry(
|
||||
ContainerInterface $container,
|
||||
?DashboardPresetRegistry $dashboardPresetRegistry = null
|
||||
): DashboardPresetRegistry {
|
||||
$dashboardPresetRegistry = $dashboardPresetRegistry ?? self::new($container, DashboardPresetRegistry::class);
|
||||
$cache = $container->get('cache.core');
|
||||
|
||||
$cacheIdentifier = $container->get(PackageDependentCacheIdentifier::class)->withPrefix(self::CACHE_IDENTIFIER_PREFIX . 'Presets')->toString();
|
||||
if (!$dashboardPresetsFromPackages = $cache->require($cacheIdentifier)) {
|
||||
$dashboardPresetsFromPackages = $container->get('dashboard.presets')->getArrayCopy();
|
||||
$cache->set($cacheIdentifier, 'return ' . var_export($dashboardPresetsFromPackages, true) . ';');
|
||||
}
|
||||
|
||||
foreach ($dashboardPresetsFromPackages as $identifier => $options) {
|
||||
// Compatibility layer for presets using the widget identifier only
|
||||
$defaultWidgets = array_map(
|
||||
static fn(string|array $defaultWidget): array => is_string($defaultWidget) ? ['identifier' => $defaultWidget] : $defaultWidget,
|
||||
$options['defaultWidgets'] ?? []
|
||||
);
|
||||
|
||||
$preset = new DashboardPreset(
|
||||
$identifier,
|
||||
$options['title'],
|
||||
$options['description'],
|
||||
$options['iconIdentifier'],
|
||||
$defaultWidgets,
|
||||
$options['showInWizard']
|
||||
);
|
||||
$dashboardPresetRegistry->registerDashboardPreset($preset);
|
||||
}
|
||||
|
||||
return $dashboardPresetRegistry;
|
||||
}
|
||||
|
||||
public static function configureWidgetGroupRegistry(
|
||||
ContainerInterface $container,
|
||||
?WidgetGroupRegistry $widgetGroupRegistry = null
|
||||
): WidgetGroupRegistry {
|
||||
$widgetGroupRegistry = $widgetGroupRegistry ?? self::new($container, WidgetGroupRegistry::class);
|
||||
$cache = $container->get('cache.core');
|
||||
|
||||
$cacheIdentifier = $container->get(PackageDependentCacheIdentifier::class)->withPrefix(self::CACHE_IDENTIFIER_PREFIX . 'WidgetGroups')->toString();
|
||||
if (!$widgetGroupsFromPackages = $cache->require($cacheIdentifier)) {
|
||||
$widgetGroupsFromPackages = $container->get('dashboard.widgetGroups')->getArrayCopy();
|
||||
$cache->set($cacheIdentifier, 'return ' . var_export($widgetGroupsFromPackages, true) . ';');
|
||||
}
|
||||
|
||||
foreach ($widgetGroupsFromPackages as $identifier => $options) {
|
||||
$group = new WidgetGroup(
|
||||
$identifier,
|
||||
$options['title']
|
||||
);
|
||||
$widgetGroupRegistry->registerWidgetGroup($group);
|
||||
}
|
||||
|
||||
return $widgetGroupRegistry;
|
||||
}
|
||||
|
||||
public static function configureDashboardPresets(ContainerInterface $container, \ArrayObject $presets): \ArrayObject
|
||||
{
|
||||
$paths = self::getPathsOfInstalledPackages();
|
||||
|
||||
foreach ($paths as $pathOfPackage) {
|
||||
$dashboardPresetsFileNameForPackage = $pathOfPackage . 'Configuration/Backend/DashboardPresets.php';
|
||||
if (file_exists($dashboardPresetsFileNameForPackage)) {
|
||||
$definedPresetsInPackage = self::requireFile($dashboardPresetsFileNameForPackage);
|
||||
if (is_array($definedPresetsInPackage)) {
|
||||
$presets->exchangeArray(array_merge($presets->getArrayCopy(), $definedPresetsInPackage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $presets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $path supplied when invoked internally through PseudoServiceProvider
|
||||
*/
|
||||
public static function configureWidgetGroups(ContainerInterface $container, \ArrayObject $widgetGroups, ?string $path = null): \ArrayObject
|
||||
{
|
||||
$paths = self::getPathsOfInstalledPackages();
|
||||
|
||||
foreach ($paths as $pathOfPackage) {
|
||||
$widgetGroupsFileNameForPackage = $pathOfPackage . 'Configuration/Backend/DashboardWidgetGroups.php';
|
||||
if (file_exists($widgetGroupsFileNameForPackage)) {
|
||||
$definedGroupsInPackage = self::requireFile($widgetGroupsFileNameForPackage);
|
||||
if (is_array($definedGroupsInPackage)) {
|
||||
$widgetGroups->exchangeArray(array_merge($widgetGroups->getArrayCopy(), $definedGroupsInPackage));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $widgetGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $path supplied when invoked internally through PseudoServiceProvider
|
||||
*/
|
||||
public static function configureWidgets(ContainerInterface $container, \ArrayObject $widgets, ?string $path = null): \ArrayObject
|
||||
{
|
||||
$paths = self::getPathsOfInstalledPackages();
|
||||
|
||||
foreach ($paths as $pathOfPackage) {
|
||||
$widgetsFileNameForPackage = $pathOfPackage . 'Configuration/Backend/DashboardWidgets.php';
|
||||
if (file_exists($widgetsFileNameForPackage)) {
|
||||
$definedWidgetsInPackage = self::requireFile($widgetsFileNameForPackage);
|
||||
if (is_array($definedWidgetsInPackage)) {
|
||||
$widgets->exchangeArray(array_merge($widgets->getArrayCopy(), $definedWidgetsInPackage));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
protected static function getPathsOfInstalledPackages(): array
|
||||
{
|
||||
$paths = [];
|
||||
$packageManager = GeneralUtility::makeInstance(PackageManager::class);
|
||||
|
||||
foreach ($packageManager->getActivePackages() as $package) {
|
||||
$paths[] = $package->getPackagePath();
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
public static function getConfigurationWarmer(ContainerInterface $container): \Closure
|
||||
{
|
||||
$cacheIdentifier = $container->get(PackageDependentCacheIdentifier::class);
|
||||
$presetsCacheIdentifier = $cacheIdentifier->withPrefix(self::CACHE_IDENTIFIER_PREFIX . 'Presets')->toString();
|
||||
$widgetGroupsCacheIdentifier = $cacheIdentifier->withPrefix(self::CACHE_IDENTIFIER_PREFIX . 'WidgetGroups')->toString();
|
||||
return static function (CacheWarmupEvent $event) use ($container, $presetsCacheIdentifier, $widgetGroupsCacheIdentifier) {
|
||||
if ($event->hasGroup('system')) {
|
||||
$cache = $container->get('cache.core');
|
||||
|
||||
$dashboardPresetsFromPackages = $container->get('dashboard.presets')->getArrayCopy();
|
||||
$cache->set($presetsCacheIdentifier, 'return ' . var_export($dashboardPresetsFromPackages, true) . ';');
|
||||
|
||||
$widgetGroupsFromPackages = $container->get('dashboard.widgetGroups')->getArrayCopy();
|
||||
$cache->set($widgetGroupsCacheIdentifier, 'return ' . var_export($widgetGroupsFromPackages, true) . ';');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static function addEventListeners(ContainerInterface $container, ListenerProvider $listenerProvider): ListenerProvider
|
||||
{
|
||||
$listenerProvider->addListener(CacheWarmupEvent::class, 'dashboard.configuration.warmer');
|
||||
|
||||
return $listenerProvider;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\ViewHelpers\Widget;
|
||||
|
||||
use TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\ElementAttributesInterface;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Renders a dashboard button
|
||||
*
|
||||
* Examples
|
||||
* ========
|
||||
*
|
||||
* ::
|
||||
*
|
||||
* <dashboard:widget.button button="{button}" class="widget-cta">
|
||||
* {f:translate(id: button.title, default: button.title)}
|
||||
* </dashboard:widget.button>
|
||||
*/
|
||||
final class ButtonViewHelper extends AbstractTagBasedViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'a';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('button', ButtonProviderInterface::class, 'Dashboard widget button', true);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$button = $this->arguments['button'];
|
||||
|
||||
$this->tag->addAttribute('href', $button->getLink() ?: '#');
|
||||
|
||||
$target = $button->getTarget();
|
||||
if ($target !== '') {
|
||||
$this->tag->addAttribute('target', $target);
|
||||
if ($target === '_blank') {
|
||||
$this->tag->addAttribute('rel', 'noreferrer');
|
||||
}
|
||||
}
|
||||
|
||||
if ($button instanceof ElementAttributesInterface) {
|
||||
$this->tag->addAttributes($button->getElementAttributes());
|
||||
}
|
||||
|
||||
$this->tag->setContent((string)$this->renderChildren());
|
||||
$this->tag->forceClosingTag(true);
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
/**
|
||||
* Provides API for widgets.
|
||||
*/
|
||||
class WidgetApi
|
||||
{
|
||||
/**
|
||||
* Provides default colors to use for charts.
|
||||
*
|
||||
* @return array{0:string, 1:string, 2:string, 3:string, 4:string} Hex codes of default colors.
|
||||
*/
|
||||
public static function getDefaultChartColors(): array
|
||||
{
|
||||
return [
|
||||
'#ff8700',
|
||||
'#a4276a',
|
||||
'#1a568f',
|
||||
'#4c7e3a',
|
||||
'#69bbb5',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class WidgetGroup
|
||||
{
|
||||
public function __construct(protected string $identifier, protected string $title) {}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->getLanguageService()->sL($this->title) ?: $this->title;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class WidgetGroupInitializationService
|
||||
{
|
||||
public function __construct(
|
||||
private WidgetGroupRegistry $widgetGroupRegistry,
|
||||
private WidgetRegistry $widgetRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Define the different groups of widgets as shown in the modal when adding a widget to the current dashboard
|
||||
*/
|
||||
public function buildWidgetGroupsConfiguration(): array
|
||||
{
|
||||
$groupConfigurations = [];
|
||||
foreach ($this->widgetGroupRegistry->getWidgetGroups() as $widgetGroup) {
|
||||
$widgets = [];
|
||||
$widgetGroupIdentifier = $widgetGroup->getIdentifier();
|
||||
|
||||
$widgetsForGroup = $this->widgetRegistry->getAvailableWidgetsForWidgetGroup($widgetGroupIdentifier);
|
||||
foreach ($widgetsForGroup as $widgetConfiguration) {
|
||||
$widgetIdentifier = $widgetConfiguration->getIdentifier();
|
||||
|
||||
$widgets[] = [
|
||||
'identifier' => $widgetIdentifier,
|
||||
'icon' => $widgetConfiguration->getIconIdentifier(),
|
||||
'label' => $this->getLanguageService()->sL($widgetConfiguration->getTitle()),
|
||||
'description' => $this->getLanguageService()->sL($widgetConfiguration->getDescription()),
|
||||
'requestType' => 'event',
|
||||
'event' => 'typo3:dashboard:widget:add',
|
||||
];
|
||||
}
|
||||
|
||||
if ($widgets === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupConfigurations[$widgetGroupIdentifier] = [
|
||||
'identifier' => $widgetGroupIdentifier,
|
||||
'label' => $widgetGroup->getTitle(),
|
||||
'items' => $widgets,
|
||||
];
|
||||
}
|
||||
|
||||
return $groupConfigurations;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class WidgetGroupRegistry
|
||||
{
|
||||
/**
|
||||
* @var WidgetGroup[]
|
||||
*/
|
||||
private array $widgetGroups = [];
|
||||
|
||||
public function getWidgetGroups(): array
|
||||
{
|
||||
return $this->widgetGroups;
|
||||
}
|
||||
|
||||
public function registerWidgetGroup(WidgetGroup $widgetGroup): void
|
||||
{
|
||||
$this->widgetGroups[$widgetGroup->getIdentifier()] = $widgetGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Dashboard\Factory\WidgetSettingsFactory;
|
||||
use TYPO3\CMS\Dashboard\Widgets\RequestAwareWidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetConfigurationInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
class WidgetRegistry
|
||||
{
|
||||
/**
|
||||
* @var array<string,WidgetConfigurationInterface>
|
||||
*/
|
||||
private $widgets = [];
|
||||
|
||||
/**
|
||||
* @var array<string, WidgetConfigurationInterface[]>
|
||||
*/
|
||||
private $widgetsPerWidgetGroup = [];
|
||||
|
||||
public function __construct(
|
||||
protected readonly ContainerInterface $container,
|
||||
protected readonly WidgetSettingsFactory $widgetSettingsFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return WidgetConfigurationInterface[]
|
||||
*/
|
||||
public function getAvailableWidgets(): array
|
||||
{
|
||||
return $this->checkPermissionOfWidgets($this->widgets);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WidgetConfigurationInterface[]
|
||||
*/
|
||||
public function getAllWidgets(): array
|
||||
{
|
||||
return $this->widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException If requested identifier does not exist.
|
||||
*/
|
||||
public function getAvailableWidget(ServerRequestInterface $request, string $identifier): WidgetRendererInterface|WidgetInterface
|
||||
{
|
||||
if (array_key_exists($identifier, $this->getAvailableWidgets())) {
|
||||
$widget = $this->container->get($this->widgets[$identifier]->getServiceName());
|
||||
if ($widget instanceof RequestAwareWidgetInterface) {
|
||||
$widget->setRequest($request);
|
||||
}
|
||||
return $widget;
|
||||
}
|
||||
throw new \InvalidArgumentException('Requested widget "' . $identifier . '" does not exist.', 1584777201);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WidgetConfigurationInterface[]
|
||||
*/
|
||||
public function getAvailableWidgetsForWidgetGroup(string $widgetGroupIdentifier): array
|
||||
{
|
||||
if (!array_key_exists($widgetGroupIdentifier, $this->widgetsPerWidgetGroup)) {
|
||||
return [];
|
||||
}
|
||||
return $this->checkPermissionOfWidgets($this->widgetsPerWidgetGroup[$widgetGroupIdentifier]);
|
||||
}
|
||||
|
||||
public function registerWidget(string $serviceName): void
|
||||
{
|
||||
$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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WidgetConfigurationInterface[] $widgets
|
||||
* @return WidgetConfigurationInterface[]
|
||||
*/
|
||||
protected function checkPermissionOfWidgets(array $widgets): array
|
||||
{
|
||||
return array_filter($widgets, function ($widget, $identifier) {
|
||||
return $this->getBackendUser()->check('available_widgets', $identifier)
|
||||
&& (!$widget instanceof WidgetConfiguration || !$widget->isAdminOnly() || $this->getBackendUser()->isAdmin());
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
}
|
||||
|
||||
public function widgetItemsProcFunc(array &$parameters): void
|
||||
{
|
||||
foreach ($this->widgets as $widget) {
|
||||
if ($widget instanceof WidgetConfiguration && $widget->isAdminOnly()) {
|
||||
continue;
|
||||
}
|
||||
$parameters['items'][] = [
|
||||
'label' => $widget->getTitle(),
|
||||
'value' => $widget->getIdentifier(),
|
||||
'icon' => $widget->getIconIdentifier(),
|
||||
'description' => $widget->getDescription(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* In case a widget should provide additional CSS files, the widget must implement this interface.
|
||||
*/
|
||||
interface AdditionalCssInterface
|
||||
{
|
||||
/**
|
||||
* This method returns an array with paths to required CSS files.
|
||||
* e.g. ['EXT:myext/Resources/Public/Css/my_widget.css']
|
||||
*/
|
||||
public function getCssFiles(): array;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* In case a widget should provide additional JavaScript files, the widget must implement this interface.
|
||||
*/
|
||||
interface AdditionalJavaScriptInterface
|
||||
{
|
||||
/**
|
||||
* This method returns an array with paths to required JS files.
|
||||
* e.g. ['EXT:myext/Resources/Public/JavaScript/my_widget.js']
|
||||
*/
|
||||
public function getJsFiles(): array;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* This interface should be used to describe a widget as restricted => it will not be able to assign to a user group
|
||||
*/
|
||||
interface AdminOnlyWidgetInterface {}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
|
||||
/**
|
||||
* 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, RequestAwareWidgetInterface, EventDataInterface, AdditionalCssInterface, JavaScriptInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly ChartDataProviderInterface $dataProvider,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly ?ButtonProviderInterface $buttonProvider = null,
|
||||
private readonly array $options = [],
|
||||
) {}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$view->assignMultiple([
|
||||
'button' => $this->buttonProvider,
|
||||
'options' => $this->options,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return $view->render('Widget/ChartWidget');
|
||||
}
|
||||
|
||||
public function getEventData(): array
|
||||
{
|
||||
return [
|
||||
'graphConfig' => [
|
||||
'type' => 'bar',
|
||||
'options' => [
|
||||
'maintainAspectRatio' => false,
|
||||
'plugins' => [
|
||||
'legend' => [
|
||||
'display' => false,
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'ticks' => [
|
||||
'beginAtZero' => true,
|
||||
],
|
||||
],
|
||||
'x' => [
|
||||
'ticks' => [
|
||||
'maxTicksLimit' => 15,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'data' => $this->dataProvider->getChartData(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return [
|
||||
JavaScriptModuleInstruction::create('@typo3/dashboard/contrib/chartjs.js'),
|
||||
JavaScriptModuleInstruction::create('@typo3/dashboard/chart-initializer.js'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use TYPO3\CMS\Backend\Backend\Bookmark\BookmarkService;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
|
||||
/**
|
||||
* Widget to display user bookmarks on the dashboard.
|
||||
*
|
||||
* The widget renders a custom element that reads bookmark data
|
||||
* from the central BookmarkStore in the top frame.
|
||||
*/
|
||||
final readonly class BookmarksWidget implements WidgetRendererInterface, JavaScriptInterface
|
||||
{
|
||||
public function __construct(
|
||||
private WidgetConfigurationInterface $configuration,
|
||||
private BackendViewFactory $backendViewFactory,
|
||||
private BookmarkService $bookmarkService,
|
||||
/** @var array{limit?: int, group?: string} */
|
||||
private array $options = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
return [
|
||||
new SettingDefinition(
|
||||
key: 'group',
|
||||
type: 'string',
|
||||
default: $this->options['group'] ?? '',
|
||||
label: 'dashboard.widget_bookmarks:widget.bookmarks.setting.groups.label',
|
||||
description: 'dashboard.widget_bookmarks:widget.bookmarks.setting.groups.description',
|
||||
readonly: array_key_exists('group', $this->options),
|
||||
enum: $this->getAvailableGroups(),
|
||||
),
|
||||
new SettingDefinition(
|
||||
key: 'limit',
|
||||
type: 'int',
|
||||
default: (int)($this->options['limit'] ?? 0),
|
||||
label: 'dashboard.widget_bookmarks:widget.bookmarks.setting.limit.label',
|
||||
description: 'dashboard.widget_bookmarks:widget.bookmarks.setting.limit.description',
|
||||
readonly: array_key_exists('limit', $this->options),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function renderWidget(WidgetContext $context): WidgetResult
|
||||
{
|
||||
$view = $this->backendViewFactory->create($context->request);
|
||||
$view->assignMultiple([
|
||||
'options' => $this->options,
|
||||
'settings' => $context->settings,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
|
||||
return new WidgetResult(
|
||||
content: $view->render('Widget/BookmarksWidget'),
|
||||
label: $this->resolveLabel($context->settings),
|
||||
refreshable: true,
|
||||
);
|
||||
}
|
||||
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return [
|
||||
JavaScriptModuleInstruction::create('@typo3/dashboard/widget/bookmarks-widget-element.js'),
|
||||
];
|
||||
}
|
||||
|
||||
private function getAvailableGroups(): array
|
||||
{
|
||||
$groups = [
|
||||
'' => 'dashboard.widget_bookmarks:widget.bookmarks.setting.showAll',
|
||||
];
|
||||
|
||||
// Only show groups that contain bookmarks
|
||||
$groupsWithBookmarks = [];
|
||||
foreach ($this->bookmarkService->getBookmarks() as $bookmark) {
|
||||
$groupsWithBookmarks[$bookmark->groupId] = true;
|
||||
}
|
||||
|
||||
foreach ($this->bookmarkService->getGroups() as $group) {
|
||||
if (isset($groupsWithBookmarks[$group->id])) {
|
||||
$groups[(string)$group->id] = $group->label;
|
||||
}
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
private function resolveLabel(SettingsInterface $settings): ?string
|
||||
{
|
||||
$group = $settings->get('group');
|
||||
if ($group !== '') {
|
||||
$groupId = is_numeric($group) ? (int)$group : $group;
|
||||
foreach ($this->bookmarkService->getGroups() as $bookmarkGroup) {
|
||||
if ($bookmarkGroup->id === $groupId) {
|
||||
$widgetTitle = $this->getLanguageService()->sL($this->configuration->getTitle());
|
||||
return $widgetTitle . ': ' . $bookmarkGroup->label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default widget title
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* 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'
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* Return the link
|
||||
*/
|
||||
public function getLink(): string;
|
||||
|
||||
/**
|
||||
* Specify the target of the link like '_blank'
|
||||
*/
|
||||
public function getTarget(): string;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function getChartData(): array;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
|
||||
/**
|
||||
* 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, RequestAwareWidgetInterface
|
||||
{
|
||||
/**
|
||||
* @var array{text: string}
|
||||
*/
|
||||
private readonly array $options;
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly ?ButtonProviderInterface $buttonProvider = null,
|
||||
array $options = [],
|
||||
) {
|
||||
$this->options = array_merge(['text' => ''], $options);
|
||||
}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$view->assignMultiple([
|
||||
'text' => $this->options['text'],
|
||||
'options' => $this->options,
|
||||
'button' => $this->buttonProvider,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return $view->render('Widget/CtaWidget');
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
|
||||
/**
|
||||
* 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, RequestAwareWidgetInterface, EventDataInterface, AdditionalCssInterface, JavaScriptInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly ChartDataProviderInterface $dataProvider,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly ?ButtonProviderInterface $buttonProvider = null,
|
||||
private readonly array $options = [],
|
||||
) {}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$view->assignMultiple([
|
||||
'button' => $this->buttonProvider,
|
||||
'options' => $this->options,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return $view->render('Widget/ChartWidget');
|
||||
}
|
||||
|
||||
public function getEventData(): array
|
||||
{
|
||||
return [
|
||||
'graphConfig' => [
|
||||
'type' => 'doughnut',
|
||||
'options' => [
|
||||
'maintainAspectRatio' => false,
|
||||
'plugins' => [
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
'position' => 'bottom',
|
||||
],
|
||||
],
|
||||
'cutoutPercentage' => 60,
|
||||
],
|
||||
'data' => $this->dataProvider->getChartData(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return [
|
||||
JavaScriptModuleInstruction::create('@typo3/dashboard/contrib/chartjs.js'),
|
||||
JavaScriptModuleInstruction::create('@typo3/dashboard/chart-initializer.js'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* In case HTML element shall contain additional attributes
|
||||
*/
|
||||
interface ElementAttributesInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, string|null>
|
||||
*/
|
||||
public function getElementAttributes(): array;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* 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 sent to the widget as JSON encoded value.
|
||||
*/
|
||||
public function getEventData(): array;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||
|
||||
/**
|
||||
* Provides potential JavaScript declarations to be loaded/initialized for a particular widget.
|
||||
*/
|
||||
interface JavaScriptInterface
|
||||
{
|
||||
/**
|
||||
* @return list<JavaScriptModuleInstruction>
|
||||
*/
|
||||
public function getJavaScriptModuleInstructions(): array;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Dashboard\Widgets\Provider\LatestBeLoginsDataProvider;
|
||||
|
||||
/**
|
||||
* This widget will show a list of recent backend user logins.
|
||||
*
|
||||
* The list contains:
|
||||
* - backend user avatar and name
|
||||
* - login time
|
||||
*
|
||||
* The following options are available during registration:
|
||||
* - limit `int` number of logins to display
|
||||
*/
|
||||
readonly class LatestBeLoginsWidget implements WidgetRendererInterface, AdminOnlyWidgetInterface
|
||||
{
|
||||
public function __construct(
|
||||
private BackendViewFactory $backendViewFactory,
|
||||
private LatestBeLoginsDataProvider $dataProvider,
|
||||
private ButtonProviderInterface $buttonProvider,
|
||||
private WidgetConfigurationInterface $configuration,
|
||||
) {}
|
||||
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
return [
|
||||
new SettingDefinition(
|
||||
key: 'limit',
|
||||
type: 'int',
|
||||
default: 10,
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestBeLogins.settings.limit',
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestBeLogins.settings.limit.description',
|
||||
options: [
|
||||
'min' => 1,
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function renderWidget(WidgetContext $context): WidgetResult
|
||||
{
|
||||
$limit = $context->settings->get('limit');
|
||||
$items = $this->dataProvider->getItems($limit);
|
||||
|
||||
$view = $this->backendViewFactory->create($context->request);
|
||||
$view->assignMultiple([
|
||||
'items' => $items,
|
||||
'button' => $this->buttonProvider,
|
||||
'configuration' => $this->configuration,
|
||||
'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
|
||||
]);
|
||||
|
||||
return new WidgetResult(
|
||||
content: $view->render('Widget/LatestBeLoginsWidget'),
|
||||
refreshable: true,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
||||
|
||||
/**
|
||||
* This widget will show a list of pages where latest changes in pages and tt_content
|
||||
* where made. The sys_history is used to get the latest changes.
|
||||
*
|
||||
* The list contains:
|
||||
* - datetime of change
|
||||
* - user (avatar, icon, name and realName)
|
||||
* - page title and rootline
|
||||
* - controls (show history, view webpage, edit page content, edit page properties)
|
||||
*
|
||||
* The following options are available during registration:
|
||||
* - limit int number of pages to show in list
|
||||
* - historyLimit int number of sys_history records to be fetched in order
|
||||
* to find limit number of pages. Increase this value
|
||||
* if number of pages in list is not achieved.
|
||||
*/
|
||||
readonly class LatestChangedPagesWidget implements WidgetRendererInterface
|
||||
{
|
||||
/**
|
||||
* @var array{limit: int, historyLimit: int}
|
||||
*/
|
||||
private array $options;
|
||||
|
||||
public function __construct(
|
||||
private BackendViewFactory $backendViewFactory,
|
||||
private ConnectionPool $connectionPool,
|
||||
private WidgetConfigurationInterface $configuration,
|
||||
private SiteFinder $siteFinder,
|
||||
array $options = [],
|
||||
) {
|
||||
$this->options = array_merge([
|
||||
'limit' => 10,
|
||||
'historyLimit' => 1000,
|
||||
], $options);
|
||||
}
|
||||
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
return [
|
||||
new SettingDefinition(
|
||||
key: 'restrictToCurrentUser',
|
||||
type: 'bool',
|
||||
default: false,
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.settings.restrictToCurrentUser',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function renderWidget(WidgetContext $context): WidgetResult
|
||||
{
|
||||
$restrictToCurrentUser = (bool)$context->settings->get('restrictToCurrentUser');
|
||||
$sysHistoryEntries = $this->getSysHistoryEntries($this->options['historyLimit'], $restrictToCurrentUser);
|
||||
$latestPages = $this->getLatestPagesFromSysHistory($sysHistoryEntries, $this->options['limit']);
|
||||
$latestPages = $this->enrichPageInformation($latestPages);
|
||||
|
||||
$view = $this->backendViewFactory->create($context->request);
|
||||
$view->assignMultiple([
|
||||
'latestPages' => $latestPages,
|
||||
'configuration' => $this->configuration,
|
||||
'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
|
||||
]);
|
||||
|
||||
return new WidgetResult(
|
||||
content: $view->render('Widget/LatestChangedPagesWidget'),
|
||||
refreshable: true,
|
||||
label: $restrictToCurrentUser ? $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.title.restrictToCurrentUser') : null,
|
||||
);
|
||||
}
|
||||
|
||||
private function getSysHistoryEntries(int $limit, bool $restrictToCurrentUser): array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilderSysHistory();
|
||||
$workspaceId = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'id');
|
||||
$queryBuilder
|
||||
->select('tablename', 'recuid', 'tstamp', 'userid')
|
||||
->from('sys_history')
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'tablename',
|
||||
[
|
||||
$queryBuilder->createNamedParameter('pages'),
|
||||
$queryBuilder->createNamedParameter('tt_content'),
|
||||
]
|
||||
),
|
||||
$queryBuilder->expr()->eq('workspace', $workspaceId),
|
||||
)
|
||||
->addOrderBy('tstamp', 'desc')
|
||||
->setMaxResults($limit);
|
||||
|
||||
if ($restrictToCurrentUser) {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
'userid',
|
||||
$queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'])
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $queryBuilder
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
|
||||
private function getLatestPagesFromSysHistory(array $history, int $limit): array
|
||||
{
|
||||
$latestPages = [];
|
||||
foreach ($history as $historyEntry) {
|
||||
$pageId = $historyEntry['recuid'];
|
||||
if ($historyEntry['tablename'] === 'tt_content') {
|
||||
$pageId = $this->getPageOfContentElement($historyEntry['recuid']);
|
||||
}
|
||||
if (!$pageId || isset($latestPages[$pageId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pageRecord = BackendUtility::readPageAccess($pageId, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if ($pageRecord === false || $pageRecord === []) {
|
||||
// Backend user has no access to show page information. Dismiss this page.
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestPages[$pageId]['history'] = $historyEntry;
|
||||
$pageRecord['_uid'] = $pageRecord['language_tag'] > 0 ? $pageRecord['l10n_parent'] : $pageRecord['uid'];
|
||||
$latestPages[$pageId]['pageRecord'] = $pageRecord;
|
||||
try {
|
||||
$latestPages[$pageId]['siteLanguage'] = $this->siteFinder->getSiteByPageId($pageRecord['_uid'])->getLanguageById($pageRecord['language_tag']);
|
||||
} catch (SiteNotFoundException $exception) {
|
||||
$latestPages[$pageId]['siteLanguage'] = null;
|
||||
}
|
||||
// Override tstamp of pageRecord with tstamp from history record if newer
|
||||
$latestPages[$pageId]['pageRecord']['tstamp'] = max($historyEntry['tstamp'], $latestPages[$pageId]['pageRecord']['tstamp']);
|
||||
|
||||
if (count($latestPages) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $latestPages;
|
||||
}
|
||||
|
||||
private function enrichPageInformation(array $latestPages): array
|
||||
{
|
||||
$userNames = BackendUtility::getUserNames('username,realName,uid');
|
||||
|
||||
foreach ($latestPages as $pageId => &$page) {
|
||||
$page['rootline'] = $this->getRootLine($pageId);
|
||||
|
||||
$uriPageId = $page['pageRecord']['language_tag'] > 0 ? $page['pageRecord']['l10n_parent'] : $page['pageRecord']['uid'];
|
||||
$page['viewLink'] = (string)PreviewUriBuilder::create($page['pageRecord'])
|
||||
->withRootLine(BackendUtility::BEgetRootLine($uriPageId))
|
||||
->buildUri();
|
||||
$page['userName'] = $userNames[$page['history']['userid']]['username'] ?? '';
|
||||
$page['realName'] = $userNames[$page['history']['userid']]['realName'] ?? '';
|
||||
}
|
||||
|
||||
return $latestPages;
|
||||
}
|
||||
|
||||
private function getPageOfContentElement(int $uid): ?int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilderForContentElements();
|
||||
$contentRecord = $queryBuilder
|
||||
->select('pid', 'sys_language_uid')
|
||||
->from('tt_content')
|
||||
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)))
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
if (!$contentRecord) {
|
||||
// Content element has been deleted
|
||||
return null;
|
||||
}
|
||||
|
||||
$queryBuilder = $this->getQueryBuilderForPages();
|
||||
if ((int)$contentRecord['language_tag'] > 0) {
|
||||
return $queryBuilder
|
||||
->select('uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($contentRecord['pid'], Connection::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter($contentRecord['language_tag'], Connection::PARAM_INT))
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative()['uid'] ?? null;
|
||||
}
|
||||
|
||||
return $queryBuilder
|
||||
->select('uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($contentRecord['pid'], Connection::PARAM_INT))
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative()['uid'] ?? null;
|
||||
}
|
||||
|
||||
private function getRootLine(int $pageId): string
|
||||
{
|
||||
$rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
|
||||
return implode(' / ', array_slice(
|
||||
array_map(
|
||||
static fn(array $page): string => $page['title'],
|
||||
array_reverse($rootLine)
|
||||
),
|
||||
0,
|
||||
-1
|
||||
));
|
||||
}
|
||||
|
||||
private function getQueryBuilderSysHistory(): QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getConnectionForTable('sys_history')->createQueryBuilder();
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
private function getQueryBuilderForContentElements(): QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getConnectionForTable('tt_content')->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
private function getQueryBuilderForPages(): QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getConnectionForTable('pages')->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
private function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* The data provider 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.
|
||||
*/
|
||||
public function getItems(): array;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
|
||||
/**
|
||||
* 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, RequestAwareWidgetInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly ListDataProviderInterface $dataProvider,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly ?ButtonProviderInterface $buttonProvider = null,
|
||||
private readonly array $options = [],
|
||||
) {}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$view->assignMultiple([
|
||||
'items' => $this->getItems(),
|
||||
'options' => $this->options,
|
||||
'button' => $this->buttonProvider,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return $view->render('Widget/ListWidget');
|
||||
}
|
||||
|
||||
protected function getItems(): array
|
||||
{
|
||||
return $this->dataProvider->getItems();
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* The dataprovider of a NumberWithIcon widget should implement this interface
|
||||
*/
|
||||
interface NumberWithIconDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Return the number that should be shown in the widget
|
||||
*/
|
||||
public function getNumber(): int;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
|
||||
/**
|
||||
* 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, RequestAwareWidgetInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly NumberWithIconDataProviderInterface $dataProvider,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly array $options = [],
|
||||
) {}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$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 $view->render('Widget/NumberWithIconWidget');
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
use TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface;
|
||||
|
||||
/**
|
||||
* Provides a button for the footer of a widget
|
||||
*/
|
||||
readonly class ButtonProvider implements ButtonProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private string $title,
|
||||
private string $link,
|
||||
private string $target = ''
|
||||
) {}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
public function getTarget(): string
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
|
||||
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
|
||||
|
||||
/**
|
||||
* Data provider for "Latest backend logins" widget.
|
||||
* Fetches successful backend user login entries from sys_log.
|
||||
*/
|
||||
readonly class LatestBeLoginsDataProvider
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionPool $connectionPool
|
||||
) {}
|
||||
|
||||
public function getItems(int $limit = 10): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_log');
|
||||
|
||||
$logEntries = $queryBuilder
|
||||
->select('uid', 'tstamp', 'userid', 'details')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'type',
|
||||
$queryBuilder->createNamedParameter(SystemLogType::LOGIN, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'action',
|
||||
$queryBuilder->createNamedParameter(SystemLogLoginAction::LOGIN, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->orderBy('tstamp', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
// Enrich with user information
|
||||
$items = [];
|
||||
$userNames = BackendUtility::getUserNames('username,realName,uid');
|
||||
|
||||
foreach ($logEntries as $entry) {
|
||||
$userId = (int)$entry['userid'];
|
||||
$userInfo = $userNames[$userId] ?? null;
|
||||
if ($userInfo !== null) {
|
||||
$items[] = [
|
||||
'timestamp' => (int)$entry['tstamp'],
|
||||
'userId' => $userId,
|
||||
'username' => $userInfo['username'] ?? '',
|
||||
'realName' => $userInfo['realName'] ?? '',
|
||||
'details' => $entry['details'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
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\NumberWithIconDataProviderInterface;
|
||||
|
||||
class NumberOfFailedLoginsDataProvider implements NumberWithIconDataProviderInterface
|
||||
{
|
||||
public function getNumber(int $secondsBack = 86400): int
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
|
||||
|
||||
return (int)$queryBuilder->count('uid')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'type',
|
||||
$queryBuilder->createNamedParameter(SystemLogType::LOGIN, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'action',
|
||||
$queryBuilder->createNamedParameter(SystemLogLoginAction::ATTEMPT, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->neq(
|
||||
'error',
|
||||
$queryBuilder->createNamedParameter(SystemLogErrorClassification::MESSAGE, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->gt(
|
||||
'tstamp',
|
||||
$queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'] - $secondsBack, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\ElementAttributesInterface;
|
||||
|
||||
/**
|
||||
* Provide link for sys log button.
|
||||
* Check whether belog is enabled and add link to module.
|
||||
* No link is returned if not enabled.
|
||||
*/
|
||||
readonly class SysLogButtonProvider implements ButtonProviderInterface, ElementAttributesInterface
|
||||
{
|
||||
public function __construct(
|
||||
private string $title,
|
||||
private string $target = '',
|
||||
private string $channel = '',
|
||||
) {}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTarget(): string
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
public function getElementAttributes(): array
|
||||
{
|
||||
if (!ExtensionManagementUtility::isLoaded('belog')) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'data-dispatch-action' => 'TYPO3.ModuleMenu.showModule',
|
||||
'data-dispatch-args-list' => 'system_log,&'
|
||||
. http_build_query(['constraint' => ['channel' => $this->channel ?: 'php']]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
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\GeneralUtility;
|
||||
use TYPO3\CMS\Dashboard\WidgetApi;
|
||||
use TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface;
|
||||
|
||||
/**
|
||||
* Provides chart data for sys log errors.
|
||||
*/
|
||||
class SysLogErrorsDataProvider implements ChartDataProviderInterface
|
||||
{
|
||||
protected array $labels = [];
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* @param int $days Number of days to gather information for.
|
||||
*/
|
||||
public function __construct(protected readonly int $days = 31) {}
|
||||
|
||||
public function getChartData(): array
|
||||
{
|
||||
$this->calculateDataForLastDays();
|
||||
|
||||
return [
|
||||
'labels' => $this->labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'),
|
||||
'backgroundColor' => WidgetApi::getDefaultChartColors()[0],
|
||||
'border' => 0,
|
||||
'data' => $this->data,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNumberOfErrorsInPeriod(int $start, int $end): int
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
|
||||
return (int)$queryBuilder
|
||||
->count('*')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'type',
|
||||
$queryBuilder->createNamedParameter(SystemLogType::ERROR, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->gte(
|
||||
'tstamp',
|
||||
$queryBuilder->createNamedParameter($start, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->lte(
|
||||
'tstamp',
|
||||
$queryBuilder->createNamedParameter($end, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
|
||||
protected function calculateDataForLastDays(): void
|
||||
{
|
||||
$format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d';
|
||||
|
||||
for ($daysBefore = $this->days; $daysBefore >= 0; $daysBefore--) {
|
||||
$this->labels[] = date($format, (int)strtotime('-' . $daysBefore . ' day'));
|
||||
$startPeriod = (int)strtotime('-' . $daysBefore . ' day 0:00:00');
|
||||
$endPeriod = (int)strtotime('-' . $daysBefore . ' day 23:59:59');
|
||||
|
||||
$this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets\Provider;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Dashboard\WidgetApi;
|
||||
use TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface;
|
||||
|
||||
readonly class TypeOfUsersChartDataProvider implements ChartDataProviderInterface
|
||||
{
|
||||
public function __construct(private LanguageServiceFactory $languageServiceFactory) {}
|
||||
|
||||
public function getChartData(): array
|
||||
{
|
||||
$languageService = $this->languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER']);
|
||||
$adminUsers = $this->getNumberOfUsers(true);
|
||||
$normalUsers = $this->getNumberOfUsers(false);
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
$languageService->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'),
|
||||
$languageService->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers'),
|
||||
],
|
||||
'datasets' => [
|
||||
[
|
||||
'backgroundColor' => WidgetApi::getDefaultChartColors(),
|
||||
'data' => [$normalUsers, $adminUsers],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNumberOfUsers(bool $admin = false): int
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
return (int)$queryBuilder
|
||||
->count('*')
|
||||
->from('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'admin',
|
||||
$queryBuilder->createNamedParameter($admin ? 1 : 0, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Interface for widgets that need the ServerRequestInterface Request.
|
||||
* The setter is called immediately after class instantiation.
|
||||
* Useful for Widgets that depend on a request, for instance when dealing
|
||||
* with views based on BackendViewFactory.
|
||||
*/
|
||||
interface RequestAwareWidgetInterface
|
||||
{
|
||||
public function setRequest(ServerRequestInterface $request): void;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Dashboard\Exception\InvalidRssFeedException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
readonly class RssWidget implements WidgetRendererInterface
|
||||
{
|
||||
public function __construct(
|
||||
private WidgetConfigurationInterface $configuration,
|
||||
#[Autowire(service: 'cache.dashboard.rss')]
|
||||
private FrontendInterface $cache,
|
||||
private BackendViewFactory $backendViewFactory,
|
||||
private ?ButtonProviderInterface $buttonProvider = null,
|
||||
/** @var array{limit?: int, lifeTime?: int, feedUrl?: string} */
|
||||
private array $options = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
return [
|
||||
new SettingDefinition(
|
||||
key: 'label',
|
||||
type: 'string',
|
||||
default: '',
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.label.label',
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.label.description',
|
||||
readonly: array_key_exists('feedUrl', $this->options),
|
||||
),
|
||||
new SettingDefinition(
|
||||
key: 'feedUrl',
|
||||
type: 'url',
|
||||
default: (string)($this->options['feedUrl'] ?? ''),
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.fieldUrl.label',
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.fieldUrl.description',
|
||||
readonly: array_key_exists('feedUrl', $this->options),
|
||||
options: [
|
||||
'pattern' => 'https?://.+',
|
||||
],
|
||||
),
|
||||
new SettingDefinition(
|
||||
key: 'limit',
|
||||
type: 'int',
|
||||
default: (int)($this->options['limit'] ?? 5),
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.limit.label',
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.limit.description',
|
||||
readonly: array_key_exists('limit', $this->options),
|
||||
),
|
||||
new SettingDefinition(
|
||||
key: 'lifeTime',
|
||||
type: 'int',
|
||||
default: (int)($this->options['lifeTime'] ?? 43200),
|
||||
label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.lifeTime.label',
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.setting.lifeTime.description',
|
||||
readonly: true,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function renderWidget(WidgetContext $context): WidgetResult
|
||||
{
|
||||
$view = $this->backendViewFactory->create($context->request);
|
||||
$feedUrl = $context->settings->get('feedUrl');
|
||||
$items = [];
|
||||
if ($feedUrl) {
|
||||
try {
|
||||
$items = $this->getFeedItems($context->settings);
|
||||
} catch (InvalidRssFeedException) {
|
||||
$view->assign('invalidFeed', true);
|
||||
}
|
||||
}
|
||||
$view->assignMultiple([
|
||||
'feedUrl' => $feedUrl,
|
||||
'items' => $items,
|
||||
'settings' => $context->settings,
|
||||
'options' => $this->options,
|
||||
'button' => $this->buttonProvider,
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return new WidgetResult(
|
||||
label: $context->settings->get('label') !== '' ? $context->settings->get('label') : null,
|
||||
content: $view->render('Widget/RssWidget'),
|
||||
refreshable: true,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getFeedItems(SettingsInterface $settings): array
|
||||
{
|
||||
$cacheHash = md5($settings->get('feedUrl') . '-' . $settings->get('limit'));
|
||||
if ($items = $this->cache->get($cacheHash)) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$feedContent = GeneralUtility::getUrl($settings->get('feedUrl'));
|
||||
if ($feedContent === false) {
|
||||
throw new InvalidRssFeedException('RSS URL could not be fetched', 1573385431);
|
||||
}
|
||||
try {
|
||||
$feedXml = simplexml_load_string($feedContent);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidRssFeedException('Received RSS feed could not be parsed.', 1573385432, $e);
|
||||
}
|
||||
|
||||
$items = match ($this->determineFeedType($feedXml)) {
|
||||
'atom' => $this->parseAtomFeed($feedXml),
|
||||
'rss' => $this->parseRssFeed($feedXml),
|
||||
default => []
|
||||
};
|
||||
|
||||
usort($items, static function ($item1, $item2) {
|
||||
return new \DateTime($item2['pubDate']) <=> new \DateTime($item1['pubDate']);
|
||||
});
|
||||
$items = array_slice($items, 0, (int)$settings->get('limit'));
|
||||
|
||||
$this->cache->set($cacheHash, $items, ['dashboard_rss'], (int)$settings->get('lifeTime'));
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function determineFeedType(\SimpleXMLElement $feedXml): string
|
||||
{
|
||||
return $feedXml->getName() === 'feed' ? 'atom' : 'rss';
|
||||
}
|
||||
|
||||
protected function parseRssFeed(\SimpleXMLElement $rssFeed): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($rssFeed->channel->item as $item) {
|
||||
$items[] = [
|
||||
'title' => trim((string)$item->title),
|
||||
'link' => trim((string)$item->link),
|
||||
'pubDate' => trim((string)$item->pubDate),
|
||||
'description' => trim(strip_tags((string)$item->description)),
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function parseAtomFeed(\SimpleXMLElement $atomFeed): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($atomFeed->entry as $entry) {
|
||||
$items[] = [
|
||||
'title' => trim((string)$entry->title),
|
||||
'link' => trim((string)($entry->link['href'] ?? '')),
|
||||
'pubDate' => trim((string)($entry->published ?? $entry->updated ?? '')),
|
||||
'description' => trim(strip_tags((string)($entry->summary ?? $entry->content ?? ''))),
|
||||
'author' => [
|
||||
'name' => trim((string)($entry->author->name ?? '')),
|
||||
'email' => trim((string)($entry->author->email ?? '')),
|
||||
'url' => trim((string)($entry->author->url ?? '')),
|
||||
],
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Information\Typo3Information;
|
||||
use TYPO3\CMS\Core\Information\Typo3Version;
|
||||
|
||||
/**
|
||||
* 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 T3GeneralInformationWidget implements WidgetInterface, RequestAwareWidgetInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly array $options = [],
|
||||
) {}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$typo3Information = new Typo3Information();
|
||||
$typo3Version = new Typo3Version();
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$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 $view->render('Widget/T3GeneralInformationWidget');
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
readonly class WidgetConfiguration implements WidgetConfigurationInterface
|
||||
{
|
||||
/**
|
||||
* @throws \InvalidArgumentException If non valid parameters were provided.
|
||||
*/
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
private string $serviceName,
|
||||
private array $groupNames,
|
||||
private string $title,
|
||||
private string $description,
|
||||
private string $iconIdentifier,
|
||||
private string $height,
|
||||
private string $width,
|
||||
private bool $adminOnly = false,
|
||||
) {
|
||||
$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($width, $allowedSizes, true)) {
|
||||
throw new \InvalidArgumentException('Width of widgets has to be small, medium or large', 1585249769);
|
||||
}
|
||||
}
|
||||
|
||||
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 isAdminOnly(): bool
|
||||
{
|
||||
return $this->adminOnly;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* 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 identifier of a widget
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Returns the service name providing the widget implementation
|
||||
*/
|
||||
public function getServiceName(): string;
|
||||
|
||||
/**
|
||||
* Returns array of group names associated to this widget
|
||||
*/
|
||||
public function getGroupNames(): array;
|
||||
|
||||
/**
|
||||
* Returns the title of a widget, this is used for the widget selector
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* Returns the description of a widget, this is used for the widget selector
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* Returns the icon identifier of a widget, this is used for the widget selector
|
||||
*/
|
||||
public function getIconIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Returns the height of a widget (small, medium, large)
|
||||
*/
|
||||
public function getHeight(): string;
|
||||
|
||||
/**
|
||||
* Returns the width of a widget (small, medium, large)
|
||||
*/
|
||||
public function getWidth(): string;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
|
||||
/**
|
||||
* Widget context containing all necessary data for widget rendering.
|
||||
*
|
||||
* This readonly value object encapsulates all the context information needed
|
||||
* by widgets during rendering, including:
|
||||
* - Widget instance identifier and raw configuration data
|
||||
* - Widget configuration and settings
|
||||
* - Current HTTP request context
|
||||
*
|
||||
* The widget context is passed to widgets implementing WidgetRendererInterface
|
||||
* and provides a clean interface for accessing widget-specific data and settings.
|
||||
*/
|
||||
final readonly class WidgetContext
|
||||
{
|
||||
public function __construct(
|
||||
public string $identifier,
|
||||
public array $rawData,
|
||||
public WidgetConfigurationInterface $configuration,
|
||||
public SettingsInterface $settings,
|
||||
public ServerRequestInterface $request,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* The WidgetInterface is the base interface for all kind of widgets.
|
||||
* All widgets must implement this interface.
|
||||
* It contains the methods which are required for all widgets.
|
||||
*/
|
||||
interface WidgetInterface
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function renderWidgetContent(): string;
|
||||
|
||||
/**
|
||||
* This method returns the options of the widget as set in the registration.
|
||||
*/
|
||||
public function getOptions(): array;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
|
||||
/**
|
||||
* The WidgetRendererInterface is the (new) base interface for all kind of widgets.
|
||||
* All widgets should implement this interface. (but can also implement WidgetInterface for the time being)
|
||||
* It contains the methods which are required for all widgets.
|
||||
*/
|
||||
interface WidgetRendererInterface
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function renderWidget(WidgetContext $context): WidgetResult;
|
||||
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getSettingsDefinitions(): array;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Dashboard\Widgets;
|
||||
|
||||
/**
|
||||
* Widget rendering result containing content and metadata.
|
||||
*
|
||||
* This readonly value object represents the result of widget rendering,
|
||||
* containing the rendered HTML content and additional metadata about
|
||||
* the widget's capabilities and state.
|
||||
*
|
||||
* The result includes:
|
||||
* - HTML content to be displayed in the dashboard
|
||||
* - Optional custom label overriding the widget's default title
|
||||
* - Refreshable flag indicating whether the widget supports refresh operations
|
||||
*/
|
||||
final readonly class WidgetResult
|
||||
{
|
||||
public function __construct(
|
||||
public string $content,
|
||||
public ?string $label = null,
|
||||
public bool $refreshable = false,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
use TYPO3\CMS\Dashboard\Controller\DashboardAjaxController;
|
||||
|
||||
return [
|
||||
// Dashboards
|
||||
'dashboard_dashboards_get' => [
|
||||
'path' => '/dashboard/dashboards/get',
|
||||
'target' => DashboardAjaxController::class . '::getDashboards',
|
||||
'methods' => ['GET'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_dashboard_add' => [
|
||||
'path' => '/dashboard/dashboard/add',
|
||||
'target' => DashboardAjaxController::class . '::addDashboard',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_dashboard_edit' => [
|
||||
'path' => '/dashboard/dashboard/edit',
|
||||
'target' => DashboardAjaxController::class . '::editDashboard',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_dashboard_update' => [
|
||||
'path' => '/dashboard/dashboard/update',
|
||||
'target' => DashboardAjaxController::class . '::updateDashboard',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_dashboard_delete' => [
|
||||
'path' => '/dashboard/dashboard/delete',
|
||||
'target' => DashboardAjaxController::class . '::deleteDashboard',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
|
||||
// Presets
|
||||
'dashboard_presets_get' => [
|
||||
'path' => '/dashboard/presets/get',
|
||||
'target' => DashboardAjaxController::class . '::getPresets',
|
||||
'methods' => ['GET'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
|
||||
// Categories
|
||||
'dashboard_categories_get' => [
|
||||
'path' => '/dashboard/categories/get',
|
||||
'target' => DashboardAjaxController::class . '::getCategories',
|
||||
'methods' => ['GET'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
|
||||
// Widgets
|
||||
'dashboard_widget_get' => [
|
||||
'path' => '/dashboard/widget/get',
|
||||
'target' => DashboardAjaxController::class . '::getWidget',
|
||||
'methods' => ['GET'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_widget_add' => [
|
||||
'path' => '/dashboard/widget/add',
|
||||
'target' => DashboardAjaxController::class . '::addWidget',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_widget_remove' => [
|
||||
'path' => '/dashboard/widget/remove',
|
||||
'target' => DashboardAjaxController::class . '::removeWidget',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_widget_settings_get' => [
|
||||
'path' => '/dashboard/widget/settings/get',
|
||||
'target' => DashboardAjaxController::class . '::getWidgetSettings',
|
||||
'methods' => ['GET'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
'dashboard_widget_settings_update' => [
|
||||
'path' => '/dashboard/widget/settings/update',
|
||||
'target' => DashboardAjaxController::class . '::updateWidgetSettings',
|
||||
'methods' => ['POST'],
|
||||
'inheritAccessFromModule' => 'dashboard',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default',
|
||||
'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default.description',
|
||||
'iconIdentifier' => 'content-dashboard',
|
||||
'defaultWidgets' => ['t3information', 'docGettingStarted'],
|
||||
'showInWizard' => false,
|
||||
],
|
||||
'empty' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.empty',
|
||||
'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.empty.description',
|
||||
'iconIdentifier' => 'content-dashboard-empty',
|
||||
'defaultWidgets' => [],
|
||||
'showInWizard' => true,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'general' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.general',
|
||||
],
|
||||
'systemInfo' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.system',
|
||||
],
|
||||
'typo3' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.typo3',
|
||||
],
|
||||
'news' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.news',
|
||||
],
|
||||
'documentation' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.documentation',
|
||||
],
|
||||
'content' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.content',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use TYPO3\CMS\Dashboard\Controller\DashboardController;
|
||||
|
||||
/**
|
||||
* Definitions for modules provided by EXT:dashboard
|
||||
*/
|
||||
return [
|
||||
'dashboard' => [
|
||||
'position' => ['before' => '*'],
|
||||
'standalone' => true,
|
||||
'access' => 'user',
|
||||
'path' => '/module/dashboard',
|
||||
'iconIdentifier' => 'module-dashboard',
|
||||
'labels' => 'dashboard.module',
|
||||
'routes' => [
|
||||
'_default' => [
|
||||
'target' => DashboardController::class . '::mainAction',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'backend',
|
||||
'core',
|
||||
],
|
||||
'imports' => [
|
||||
'@typo3/dashboard/' => [
|
||||
'path' => 'EXT:dashboard/Resources/Public/JavaScript/',
|
||||
'exclude' => [
|
||||
'EXT:dashboard/Resources/Public/JavaScript/Contrib/',
|
||||
],
|
||||
],
|
||||
'chart.js' => 'EXT:dashboard/Resources/Public/JavaScript/Contrib/chartjs.js',
|
||||
// legacy, has been renamed "chart.js"
|
||||
'@typo3/dashboard/contrib/chartjs.js' => 'EXT:dashboard/Resources/Public/JavaScript/Contrib/chartjs.js',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace TYPO3\CMS\Dashboard;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
|
||||
$containerBuilder->addCompilerPass(new DependencyInjection\DashboardWidgetPass('dashboard.widget'));
|
||||
};
|
||||
@@ -0,0 +1,252 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
TYPO3\CMS\Dashboard\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration:
|
||||
autowire: false
|
||||
|
||||
dashboard.buttons.syslogErrors:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider'
|
||||
arguments:
|
||||
$title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText'
|
||||
|
||||
dashboard.buttons.syslogUsers:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider'
|
||||
arguments:
|
||||
$title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogUsers.buttonText'
|
||||
$channel: 'user'
|
||||
|
||||
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://news.typo3.com'
|
||||
$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/main/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/main/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/main/en-us/Index.html'
|
||||
$target: '_blank'
|
||||
|
||||
dashboard.widget.rss:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 'rss'
|
||||
groupNames: 'news'
|
||||
title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.title'
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_rss.xlf:widget.rss.description'
|
||||
iconIdentifier: 'content-widget-rss'
|
||||
height: 'large'
|
||||
width: 'medium'
|
||||
|
||||
dashboard.widget.t3news:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
$buttonProvider: '@dashboard.buttons.t3news'
|
||||
$options:
|
||||
feedUrl: 'https://typo3.org/rss'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 't3news'
|
||||
groupNames: 'news'
|
||||
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'
|
||||
$buttonProvider: '@dashboard.buttons.syslogErrors'
|
||||
$options:
|
||||
refreshAvailable: true
|
||||
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'
|
||||
height: 'medium'
|
||||
width: 'medium'
|
||||
|
||||
dashboard.widget.docGettingStarted:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget'
|
||||
arguments:
|
||||
$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:
|
||||
$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:
|
||||
$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'
|
||||
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:
|
||||
$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'
|
||||
height: 'medium'
|
||||
|
||||
dashboard.widget.t3securityAdvisories:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
$buttonProvider: '@dashboard.buttons.t3securityAdvisories'
|
||||
$options:
|
||||
feedUrl: 'https://typo3.org/rss-security'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 't3securityAdvisories'
|
||||
groupNames: 'news'
|
||||
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'
|
||||
$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'
|
||||
|
||||
dashboard.widget.bookmarks:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\BookmarksWidget'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 'bookmarks'
|
||||
groupNames: 'content'
|
||||
title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_bookmarks.xlf:widget.bookmarks.title'
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang_widget_bookmarks.xlf:widget.bookmarks.description'
|
||||
iconIdentifier: 'content-bookmark'
|
||||
height: 'medium'
|
||||
width: 'medium'
|
||||
|
||||
dashboard.widget.latestChangedPages:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\LatestChangedPagesWidget'
|
||||
arguments:
|
||||
$options:
|
||||
refreshAvailable: true
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 'latestChangedPages'
|
||||
groupNames: 'content'
|
||||
title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.title'
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.description'
|
||||
iconIdentifier: 'content-widget-list'
|
||||
height: 'medium'
|
||||
width: 'medium'
|
||||
|
||||
dashboard.widget.latestBeLogins:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\LatestBeLoginsWidget'
|
||||
arguments:
|
||||
$dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\LatestBeLoginsDataProvider'
|
||||
$buttonProvider: '@dashboard.buttons.syslogUsers'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 'latestBeLogins'
|
||||
groupNames: 'systemInfo'
|
||||
title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestBeLogins.title'
|
||||
description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestBeLogins.description'
|
||||
iconIdentifier: 'content-widget-list'
|
||||
height: 'medium'
|
||||
width: 'small'
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
defined('TYPO3') or die();
|
||||
|
||||
call_user_func(static function () {
|
||||
$additionalColumns = [
|
||||
'availableWidgets' => [
|
||||
'label' => 'core.tca:availableWidgets',
|
||||
'config' => [
|
||||
'type' => 'select',
|
||||
'renderType' => 'selectCheckBox',
|
||||
'itemsProcFunc' => \TYPO3\CMS\Dashboard\WidgetRegistry::class . '->widgetItemsProcFunc',
|
||||
'size' => 5,
|
||||
'autoSizeMax' => 50,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('be_groups', $additionalColumns);
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
|
||||
'be_groups',
|
||||
'availableWidgets',
|
||||
'',
|
||||
'after:groupMods'
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'ctrl' => [
|
||||
'title' => 'dashboard.db:be_dashboard',
|
||||
'label' => 'title',
|
||||
'tstamp' => 'tstamp',
|
||||
'crdate' => 'crdate',
|
||||
'adminOnly' => true,
|
||||
'rootLevel' => 1,
|
||||
'delete' => 'deleted',
|
||||
'hideTable' => true,
|
||||
'enablecolumns' => [
|
||||
'disabled' => 'hidden',
|
||||
'starttime' => 'starttime',
|
||||
'endtime' => 'endtime',
|
||||
],
|
||||
'default_sortby' => 'crdate DESC',
|
||||
'typeicon_classes' => [
|
||||
'default' => 'content-dashboard',
|
||||
],
|
||||
],
|
||||
'columns' => [
|
||||
// The owner of the dashboard
|
||||
'cruser_id' => [
|
||||
'config' => [
|
||||
'type' => 'passthrough',
|
||||
],
|
||||
],
|
||||
'identifier' => [
|
||||
'label' => 'dashboard.db:identifier',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'size' => 30,
|
||||
'max' => 255,
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
'label' => 'dashboard.db:title',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'size' => 30,
|
||||
'max' => 255,
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'types' => [
|
||||
'1' => [
|
||||
'showitem' => '
|
||||
--div--;core.form.tabs:general,
|
||||
identifier,title,
|
||||
--div--;core.form.tabs:access,
|
||||
hidden, --palette--;;timeRestriction,
|
||||
--div--;core.form.tabs:extended,
|
||||
',
|
||||
],
|
||||
],
|
||||
'palettes' => [
|
||||
'timeRestriction' => ['showitem' => 'starttime, endtime'],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _configuration:
|
||||
|
||||
=============
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Target group: **Developers** and **Integrators**
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:titlesonly:
|
||||
|
||||
WidgetRegistration
|
||||
WidgetGroupCreation
|
||||
WidgetPresets
|
||||
WidgetSettings
|
||||
WidgetTemplate
|
||||
PermissionHandlingOfWidgets
|
||||
@@ -0,0 +1,17 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _permission-handling-of-widgets:
|
||||
|
||||
======================
|
||||
Permissions of widgets
|
||||
======================
|
||||
|
||||
Backend users marked as administrator have always access to all registered widgets.
|
||||
|
||||
Other backend users can be restricted via :guilabel:`Access List > Dashboard widgets` inside of user groups.
|
||||
Each widget needs to be explicitly allowed.
|
||||
|
||||
.. figure:: /Images/AccessRestriction.png
|
||||
:align: center
|
||||
|
||||
Granting access to dashboard widgets for backend users.
|
||||
@@ -0,0 +1,45 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _create-widget-group:
|
||||
|
||||
===================
|
||||
Create widget group
|
||||
===================
|
||||
|
||||
Widget groups are used to group widgets into tabs.
|
||||
This will have an effect when adding new widgets to an dashboard.
|
||||
See :ref:`adding-widgets` to get an idea of the UI.
|
||||
|
||||
Groups are defined as PHP array:
|
||||
|
||||
.. code-block:: php
|
||||
:caption: Example from EXT:dashboard/Configuration/Backend/DashboardWidgetGroups.php
|
||||
|
||||
<?php
|
||||
|
||||
return [
|
||||
'general' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.general',
|
||||
],
|
||||
'systemInfo' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.system',
|
||||
],
|
||||
'typo3' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.typo3',
|
||||
],
|
||||
'news' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.news',
|
||||
],
|
||||
'documentation' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.documentation',
|
||||
],
|
||||
];
|
||||
|
||||
The file has to return an array of groups.
|
||||
Each group consists of an array key used as identifier and an single option :php:`title`.
|
||||
The title will be processed through translation and can be an ``LLL`` reference.
|
||||
|
||||
Each extension can create arbitrary widget groups.
|
||||
|
||||
Widgets can be assigned to multiple groups using the :confval:`widget-tag-groupNames`.
|
||||
Please read :ref:`register-new-widget` to understand how this is done.
|
||||
@@ -0,0 +1,124 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _dashboard-presets:
|
||||
|
||||
=================
|
||||
Dashboard Presets
|
||||
=================
|
||||
|
||||
It is possible to configure presets of dashboards.
|
||||
The extension already ships a ``default`` as well as an ``empty`` dashboard preset.
|
||||
|
||||
.. _create-preset:
|
||||
|
||||
Create preset
|
||||
-------------
|
||||
|
||||
New presets can be configured:
|
||||
|
||||
.. code-block:: php
|
||||
:caption: Example from EXT:dashboard/Configuration/Backend/DashboardPresets.php
|
||||
|
||||
<?php
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default',
|
||||
'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default.description',
|
||||
'iconIdentifier' => 'content-dashboard',
|
||||
'defaultWidgets' => [
|
||||
't3information',
|
||||
't3news',
|
||||
'docGettingStarted',
|
||||
[
|
||||
'identifier' => 'rss',
|
||||
'settings' => [
|
||||
'label' => 'My RSS Feed',
|
||||
'feedUrl' => 'https://typo3.org/rss',
|
||||
'limit' => 10,
|
||||
],
|
||||
],
|
||||
],
|
||||
'showInWizard' => false,
|
||||
],
|
||||
];
|
||||
|
||||
The file has to return an array with all presets.
|
||||
Each prefix itself is an array, with an identifier as key.
|
||||
The identifier is used to configure presets for users, see :ref:`configure-preset-for-user`.
|
||||
|
||||
Each preset consists of the following options:
|
||||
|
||||
.. php:class:: TYPO3\CMS\Dashboard\DashboardPreset
|
||||
|
||||
.. confval:: title
|
||||
:type: string
|
||||
:name: widget-presets-title
|
||||
|
||||
The title used for the preset. E.g. a ``LLL:EXT:`` reference..
|
||||
|
||||
.. confval:: description
|
||||
:type: string
|
||||
:name: widget-presets-description
|
||||
|
||||
The description used for the preset. E.g. a ``LLL:EXT:`` reference..
|
||||
|
||||
.. confval:: iconIdentifier
|
||||
:type: string
|
||||
:name: widget-presets-iconIdentifier
|
||||
|
||||
The identifier of the icon to use.
|
||||
|
||||
.. confval:: defaultWidgets
|
||||
:type: array
|
||||
:name: widget-presets-defaultWidgets
|
||||
|
||||
An array of widget identifiers, or fine-grained structure, that should be part of the dashboard preset.
|
||||
|
||||
Widgets are always filtered by permissions of each user.
|
||||
Only widgets with access are actually part of the dashboard.
|
||||
Have a look at :ref:`permission-handling-of-widgets` to understand how to handle permissions.
|
||||
|
||||
.. confval:: showInWizard
|
||||
:type: bool
|
||||
:name: widget-presets-showInWizard
|
||||
|
||||
Boolean value to indicate, whether this preset should be visible in the wizard,
|
||||
when creating new dashboards, see :ref:`adding-dashboard`.
|
||||
|
||||
This can be disabled, to add presets via :ref:`configure-preset-for-user`, without
|
||||
showing up in the wizard.
|
||||
|
||||
.. _configure-preset-for-user:
|
||||
|
||||
Configure preset for user
|
||||
-------------------------
|
||||
|
||||
To define the default preset for a backend user, the following User TSconfig can be added:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
options.dashboard.dashboardPresetsForNewUsers = default
|
||||
|
||||
Where ``default`` is the identifier of the preset.
|
||||
Even a comma separated list of identifiers is possible:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
options.dashboard.dashboardPresetsForNewUsers = default, companyDefault
|
||||
|
||||
It is also possible to add another dashboard to the set of dashboards:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
options.dashboard.dashboardPresetsForNewUsers := addToList(anotherOne)
|
||||
|
||||
If nothing is configured, ``default`` will be used as identifier.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`t3tsref:userthetsconfigfield` section of TSconfig manual
|
||||
explains how to set or register TSconfig for user.
|
||||
|
||||
:ref:`t3tsref:typoscript-syntax-syntax-value-modification` explains the usage of
|
||||
:typoscript:`:=` TypoScript operator.
|
||||
@@ -0,0 +1,326 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
Widgets need to be provided by an extension, e.g. by ext:dashboard.
|
||||
They are provided as a PHP class with specific feature sets.
|
||||
Each of the widgets can be registered with different configurations as documented below.
|
||||
|
||||
.. include:: /Shared/DifferenceRegistrationAndImplementation.rst.txt
|
||||
|
||||
The below example will use the RSS Widget as a concrete example.
|
||||
|
||||
.. _register-new-widget:
|
||||
|
||||
===================
|
||||
Register new Widget
|
||||
===================
|
||||
|
||||
Registration happens through :ref:`Dependency Injection <t3coreapi:DependencyInjection>`
|
||||
either in :file:`Services.yaml` or :file:`Services.php`.
|
||||
Both files can exist and will be merged.
|
||||
|
||||
:file:`Services.yaml` is recommended and easier to write,
|
||||
while :file:`Services.php` provide way more flexibility.
|
||||
|
||||
.. _register-new-widget-naming:
|
||||
|
||||
Naming widgets
|
||||
--------------
|
||||
|
||||
Widgets receive a name in form of ``dashboard.widget.vendor.ext_key.widgetName``.
|
||||
|
||||
``vendor``
|
||||
Should be a snaked version of composer vendor.
|
||||
|
||||
``ext_key``
|
||||
Should be the extension key.
|
||||
|
||||
This prevents naming conflicts if multiple 3rd Party extensions are installed.
|
||||
|
||||
.. _register-new-widget-services:
|
||||
|
||||
Services.yaml file
|
||||
------------------
|
||||
|
||||
In order to turn the PHP class :php:`\TYPO3\CMS\Dashboard\Widgets\RssWidget` into an actual widget,
|
||||
the following service registration can be used:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: Excerpt from EXT:dashboard/Configuration/Services.yaml
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
TYPO3\CMS\Dashboard\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
dashboard.widget.t3news:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
$buttonProvider: '@dashboard.buttons.t3news'
|
||||
$options:
|
||||
feedUrl: 'https://www.typo3.org/rss'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 't3news'
|
||||
groupNames: 'news'
|
||||
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'
|
||||
|
||||
The beginning of the file is not related to the widget itself, but dependency injection in general,
|
||||
see: :ref:`t3coreapi:configure-dependency-injection-in-extensions`.
|
||||
|
||||
.. _register-new-widget-service-configuration:
|
||||
|
||||
Service configuration
|
||||
"""""""""""""""""""""
|
||||
|
||||
The last block configured a service called :yaml:`dashboard.widget.t3news`.
|
||||
|
||||
This service is configured to use the existing PHP class :php:`TYPO3\CMS\Dashboard\Widgets\RssWidget`.
|
||||
When creating the instance of this class, an array is provided for the constructor argument :php:`$options`.
|
||||
This way the same PHP class can be used with different configuration to create new widgets.
|
||||
|
||||
The following keys are defined for the service:
|
||||
|
||||
.. confval:: class
|
||||
:type: string
|
||||
:name: widget-class
|
||||
:Example: :php:`TYPO3\CMS\Dashboard\Widgets\RssWidget`
|
||||
|
||||
Defines the concrete PHP class to use as the implementation of the widget.
|
||||
|
||||
.. confval:: arguments
|
||||
:type: map
|
||||
:name: widget-arguments
|
||||
|
||||
A set of key-value pairs, where the keys are the argument names and the
|
||||
values are the corresponding argument values. The specific arguments depend
|
||||
on the widget being configured, and each widget can define custom arguments.
|
||||
|
||||
Documentation for the provided widgets is available at :ref:`widgets`.
|
||||
|
||||
.. confval:: tags
|
||||
:type: array of dictionaries
|
||||
:name: widget-tags
|
||||
|
||||
Registers the service as an actual widget for :composer:`typo3/cms-dashboard`. Each entry in
|
||||
the array is a dictionary that can include various properties like name,
|
||||
identifier, groupNames, and so on, used to categorize and identify the widget.
|
||||
|
||||
See :ref:`register-new-widget-tags-section`.
|
||||
|
||||
.. _register-new-widget-tags-section:
|
||||
|
||||
Tags Section
|
||||
""""""""""""
|
||||
|
||||
In order to turn the instance into a widget, the tag `dashboard.widget` is configured in `tags` section.
|
||||
The following options are mandatory and need to be provided:
|
||||
|
||||
.. confval:: name
|
||||
:type: string
|
||||
:name: widget-tag-name
|
||||
:required:
|
||||
:Example: `dashboard.widget`
|
||||
|
||||
Always has to be `dashboard.widget`.
|
||||
Defines that this tag configures the service to be registered as a widget for
|
||||
ext:dashboard.
|
||||
|
||||
.. confval:: identifier
|
||||
:type: string
|
||||
:name: widget-tag-identifier
|
||||
:required:
|
||||
:Example: `t3news`
|
||||
|
||||
Used to store which widgets are currently assigned to dashboards.
|
||||
Furthermore, it is used to allow access control, see :ref:`permission-handling-of-widgets`.
|
||||
|
||||
.. confval:: groupNames
|
||||
:type: string (comma-separated)
|
||||
:name: widget-tag-groupNames
|
||||
:required:
|
||||
:Example: `news`
|
||||
|
||||
Defines which groups should contain the widget.
|
||||
Used when adding widgets to a dashboard to group related widgets in tabs.
|
||||
Multiple names can be defined as a comma-separated string, e.g.: `typo3, general`.
|
||||
|
||||
See :ref:`create-widget-group` regarding how to create new widget groups.
|
||||
There is no difference between custom groups and existing groups.
|
||||
Widgets are registered to all groups by their name.
|
||||
|
||||
.. confval:: title
|
||||
:type: string (language reference)
|
||||
:name: widget-tag-title
|
||||
:required:
|
||||
:Example: `LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title`
|
||||
|
||||
Defines the title of the widget. Language references are resolved.
|
||||
|
||||
.. confval:: description
|
||||
:type: string (language reference)
|
||||
:name: widget-tag-description
|
||||
:required:
|
||||
:Example: `LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description`
|
||||
|
||||
Defines the description of the widget. Language references are resolved.
|
||||
|
||||
.. confval:: iconIdentifier
|
||||
:type: string
|
||||
:name: widget-tag-iconIdentifier
|
||||
:required:
|
||||
:Example: `content-widget-rss`
|
||||
|
||||
One of the registered icons.
|
||||
Icons can be registered through :ref:`t3coreapi:icon`.
|
||||
|
||||
The following options are optional and have default values which will be used if not defined:
|
||||
|
||||
.. confval:: height
|
||||
:type: string
|
||||
:name: widget-tag-height
|
||||
:Example: `large`
|
||||
|
||||
Has to be a string value: `large`, `medium`, or `small`.
|
||||
|
||||
.. confval:: width
|
||||
:type: string
|
||||
:name: widget-tag-width
|
||||
:Example: `medium`
|
||||
|
||||
Has to be a string value: `large`, `medium`, or `small`.
|
||||
|
||||
.. _register-new-widget-splitting:
|
||||
|
||||
Splitting up Services.yaml
|
||||
--------------------------
|
||||
|
||||
In case the :file:`Services.yaml` is getting to large, it can be split up.
|
||||
The official documentation can be found at `symfony.com <https://symfony.com/doc/current/service_container/import.html>`__.
|
||||
An example to split up all Widget related configuration would look like:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: Excerpt from EXT:dashboard/Configuration/Services.yaml
|
||||
|
||||
imports:
|
||||
- { resource: Backend/DashboardWidgets.yaml }
|
||||
|
||||
.. note::
|
||||
|
||||
Note that you have to repeat all necessary information, e.g. :yaml:`services:` section with :yaml:`_defaults:` again.
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: Excerpt from EXT:dashboard/Configuration/Backend/DashboardWidgets.yaml
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
TYPO3\CMS\Dashboard\Widgets\:
|
||||
resource: '../Classes/Widgets/*'
|
||||
|
||||
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://news.typo3.com'
|
||||
$target: '_blank'
|
||||
|
||||
dashboard.widget.t3news:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
$buttonProvider: '@dashboard.buttons.t3news'
|
||||
$options:
|
||||
feedUrl: 'https://www.typo3.org/rss'
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
identifier: 't3news'
|
||||
groupNames: 'news'
|
||||
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'
|
||||
|
||||
|
||||
.. _register-new-widget-services-php:
|
||||
|
||||
Services.php File
|
||||
-----------------
|
||||
|
||||
This is not intended for integrators but developers only, as this involves PHP experience.
|
||||
|
||||
The typical use case should be solved via :file:`Services.yaml`.
|
||||
But for more complex situations, it is possible to register widgets via :file:`Services.php`.
|
||||
Even if :file:`Services.php` contains PHP, it is only executed during compilation of the dependency injection container.
|
||||
Therefore, it is not possible to check for runtime information like URLs, users, configuration or packages.
|
||||
|
||||
Instead, this approach can be used to register widgets only if their service dependencies are available.
|
||||
The :php:`ContainerBuilder` instance provides a method :php:`hasDefinition()`
|
||||
that may be used to check for optional dependencies.
|
||||
Make sure to declare the optional dependencies in :file:`composer.json` as
|
||||
suggested extensions to ensure packages are ordered correctly in order for
|
||||
services to be registered with deterministic ordering.
|
||||
|
||||
The following example demonstrates how a widget can be registered via :file:`Services.php`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace Vendor\ExtName;
|
||||
|
||||
use Vendor\ExtName\Widgets\ExampleWidget;
|
||||
use Vendor\ExtName\Widgets\Provider\ExampleProvider;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Report\Status;
|
||||
|
||||
return function (ContainerConfigurator $configurator, ContainerBuilder $containerBuilder) {
|
||||
$services = $configurator->services();
|
||||
|
||||
if ($containerBuilder->hasDefinition(Status::class)) {
|
||||
$services->set('widgets.dashboard.widget.exampleWidget')
|
||||
->class(ExampleWidget::class)
|
||||
->arg('$buttonProvider', new Reference(ExampleProvider::class))
|
||||
->arg('$options', ['template' => 'Widget/ExampleWidget'])
|
||||
->tag('dashboard.widget', [
|
||||
'identifier' => 'widgets-exampleWidget',
|
||||
'groupNames' => 'systemInfo',
|
||||
'title' => 'LLL:EXT:ext_key/Resources/Private/Language/locallang.xlf:widgets.dashboard.widget.exampleWidget.title',
|
||||
'description' => 'LLL:EXT:ext_key/Resources/Private/Language/locallang.xlf:widgets.dashboard.widget.exampleWidget.description',
|
||||
'iconIdentifier' => 'content-widget-list',
|
||||
'height' => 'medium',
|
||||
'width' => 'medium'
|
||||
])
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
Above example will register a new widget called ``widgets.dashboard.widget.exampleWidget``.
|
||||
The widget is only registered, in case the extension "reports" is enabled, which
|
||||
results in the availablity of the :php:`TYPO3\CMS\Report\Status` during container compile time.
|
||||
|
||||
Configuration is done in the same way as with :file:`Services.yaml`, except a PHP API is used.
|
||||
The :php:`new Reference` equals to :yaml:`@` inside the YAML, to reference another service.
|
||||
:yaml:`arguments:` are registered via :php:`->arg()` method call.
|
||||
And :yaml:`tags:` are added via :php:`->tag()` method call.
|
||||
|
||||
Using this approach, it is possible to provide widgets that depend on 3rd party code,
|
||||
without requiring this 3rd party code.
|
||||
Instead the 3rd party code can be suggested and is supported if its installed.
|
||||
|
||||
Further information regarding how :file:`Services.php` works in general, can be found
|
||||
at `symfony.com <https://symfony.com/doc/current/components/dependency_injection.html>`_.
|
||||
Make sure to switch code examples from YAML to PHP.
|
||||
@@ -0,0 +1,59 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _settings:
|
||||
|
||||
=====================================
|
||||
Adjust settings of registered widgets
|
||||
=====================================
|
||||
|
||||
.. versionadded:: 14.0
|
||||
`Configurable Dashboard Widgets <https://docs.typo3.org/permalink/changelog:feature-107036-1738837673>`_
|
||||
have been introduced with TYPO3 14.0.
|
||||
|
||||
.. contents:: Table of contents
|
||||
|
||||
.. _adjust-settings-of-widget-why:
|
||||
.. _configurable-widgets:
|
||||
|
||||
Configurable dashboard widgets
|
||||
------------------------------
|
||||
|
||||
.. versionadded:: 14.0
|
||||
|
||||
Dashboard widgets can be configured on a per-instance level using the Settings
|
||||
API. This allows widget authors to define configurable settings that editors
|
||||
can modify directly from the dashboard interface, making widgets more
|
||||
flexible and user-friendly.
|
||||
|
||||
Examples are URLs for RSS feeds, limits on displayed items, or categories for
|
||||
filtering content.
|
||||
|
||||
Each widget instance maintains its own configuration, enabling multiple
|
||||
instances of the same widget type with different settings on the same or
|
||||
different dashboards.
|
||||
|
||||
Configurable widgets display a `settings (cog) icon <https://docs.typo3.org/permalink/typo3/cms-dashboard:widgets-configuration>`_
|
||||
and allow editors to configure the widget in a modal dialog.
|
||||
|
||||
Extension authors can implement :php-short:`\TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface`
|
||||
to make their widgets configurable:
|
||||
`Configurable dashboard widget implementation <https://docs.typo3.org/permalink/typo3/cms-dashboard:configurable-widget-implementation>`_.
|
||||
|
||||
.. _adjust-settings-of-widget:
|
||||
|
||||
Adjust settings of registered widgets
|
||||
=====================================
|
||||
|
||||
Each widget is registered with an identifier, and all :file:`Services.*` files are merged.
|
||||
Therefore it is possible to override widgets.
|
||||
In order to override, the extension which should override has to be loaded after the extension that registered the widget.
|
||||
|
||||
Concrete options depend on the widget to configure.
|
||||
Each widget should provide documentation covering all possible options and their meaning.
|
||||
For delivered widgets by EXT:dashboard see :ref:`widgets`.
|
||||
|
||||
In case a widget defined by EXT:dashboard should be adjusted,
|
||||
the extension has to define a dependency to EXT:dashboard.
|
||||
|
||||
Afterwards the widget can be registered again, with different options. See
|
||||
:ref:`register-new-widget` to get an in depth example of how to register a widget.
|
||||
@@ -0,0 +1,26 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _adjust-template-of-widget:
|
||||
|
||||
==========================
|
||||
Adjust template of widgets
|
||||
==========================
|
||||
|
||||
When adding own widgets, it might be necessary to provide custom templates.
|
||||
In such a case the file path containing the template files needs to be added.
|
||||
|
||||
This is done using a :file:`Configuration/page.tsconfig` file, see
|
||||
:doc:`changelog <ext_core:Changelog/12.0/Feature-96812-OverrideBackendTemplatesWithTSconfig>` and
|
||||
:doc:`changelog <ext_core:Changelog/12.0/Feature-96614-AutomaticInclusionOfPageTsConfigOfExtensions>`
|
||||
for details on this:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
# Pattern: templates.typo3/cms-dashboard."something-unique" = "overriding-extension-composer-name":"entry-path"
|
||||
templates.typo3/cms-dashboard.1644485473 = myvendor/myext:Resources/Private
|
||||
|
||||
A template file can then be added to path :file:`Resources/Private/Templates/Widgets/MyExtensionsGreatWidget.html`
|
||||
and is referenced in the PHP class using :php:`->render('Widgets/MyExtensionsGreatWidget');`. The registration
|
||||
into namespace :php:`typo3/cms-dashboard` is shared between all extensions. It is thus a good idea to give
|
||||
template file names unique names (for instance by prefixing them with the extension name), to avoid situations
|
||||
where templates from multiple extensions that provide different widgets override each other.
|
||||
@@ -0,0 +1,111 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _adding-buttons:
|
||||
|
||||
=======================
|
||||
Adding button to Widget
|
||||
=======================
|
||||
|
||||
.. php:namespace:: TYPO3\CMS\Dashboard\Widgets
|
||||
|
||||
In order to add a button to a widget, a new dependency to an :php:`ButtonProviderInterface` can be added.
|
||||
|
||||
.. _adding-buttons-template:
|
||||
|
||||
Template
|
||||
--------
|
||||
|
||||
The output itself is done inside of the Fluid template, for example :file:`Resources/Private/Templates/Widget/RssWidget.html`:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<f:if condition="{button}">
|
||||
<a href="{button.link}" target="{button.target}" class="widget-cta">
|
||||
{f:translate(id: button.title, default: button.title)}
|
||||
</a>
|
||||
</f:if>
|
||||
|
||||
.. _adding-buttons-configuration:
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The configuration is done through an configured Instance of the dependency, for example :file:`Services.yaml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
services:
|
||||
# …
|
||||
|
||||
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://news.typo3.com'
|
||||
$target: '_blank'
|
||||
|
||||
dashboard.widget.t3news:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
# …
|
||||
$buttonProvider: '@dashboard.buttons.t3news'
|
||||
# …
|
||||
|
||||
See also: :php:`\TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider`.
|
||||
|
||||
.. confval:: $title
|
||||
:type: string
|
||||
:name: button-title
|
||||
|
||||
The title used for the button. E.g. an ``LLL:EXT:`` reference like
|
||||
``LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems``.
|
||||
|
||||
.. confval:: $link
|
||||
:type: string
|
||||
:name: button-link
|
||||
|
||||
The link to use for the button. Clicking the button will open the link.
|
||||
|
||||
.. confval:: $target
|
||||
:type: string
|
||||
:name: button-target
|
||||
|
||||
The target of the link, e.g. ``_blank``.
|
||||
``LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems``.
|
||||
|
||||
|
||||
.. _adding-buttons-implementation:
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
An example implementation could look like this:
|
||||
|
||||
.. code-block:: php
|
||||
:caption: Classes/Widgets/RssWidget.php
|
||||
|
||||
class RssWidget implements WidgetInterface
|
||||
{
|
||||
public function __construct(
|
||||
// …
|
||||
private readonly ButtonProviderInterface $buttonProvider = null,
|
||||
// …
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
// …
|
||||
$this->view->assignMultiple([
|
||||
// …
|
||||
'button' => $this->buttonProvider,
|
||||
// …
|
||||
]);
|
||||
// …
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
:navigation-title: Configurable widgets
|
||||
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _configurable-widget-implementation:
|
||||
|
||||
============================================
|
||||
Configurable dashboard widget implementation
|
||||
============================================
|
||||
|
||||
.. versionadded:: 14.0
|
||||
`Configurable Dashboard Widgets <https://docs.typo3.org/permalink/changelog:feature-107036-1738837673>`_
|
||||
have been introduced with TYPO3 14.0.
|
||||
|
||||
Widget authors can implement configurable widgets by using to the
|
||||
renderer interface :php:`TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface`
|
||||
which allows to defining settings in their widget renderer.
|
||||
|
||||
Settings are automatically validated and processed using the Settings API.
|
||||
All types that are available for site settings definition are available:
|
||||
`Definition types <https://docs.typo3.org/permalink/t3coreapi:definition-types>`_.
|
||||
|
||||
.. seealso::
|
||||
:php:`TYPO3\CMS\Dashboard\Widgets\RssWidget` is a configurable widget
|
||||
implementation.
|
||||
|
||||
.. _configurable-widget-implementation-example:
|
||||
|
||||
Example: A configurable widget implementation
|
||||
=============================================
|
||||
|
||||
.. literalinclude:: _codesnippets/_ConfigurableWidget.php.inc
|
||||
:language: php
|
||||
:caption: EXT:my_extension/Classes/Widgets/ConfigurableWidget.php
|
||||
@@ -0,0 +1,174 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _graph-widget-implementation:
|
||||
|
||||
======================
|
||||
Implement graph widget
|
||||
======================
|
||||
|
||||
.. php:namespace:: TYPO3\CMS\Dashboard\Widgets
|
||||
|
||||
First of all a new data provider is required, which will provide the data for the chart.
|
||||
Next the data will be provided to the widget instance, which will be rendered with JavaScript modules and Css.
|
||||
|
||||
To make the dashboard aware of this workflow, some interfaces come together:
|
||||
|
||||
* :php:`EventDataInterface`
|
||||
|
||||
* :php:`AdditionalCssInterface`
|
||||
|
||||
Also the existing template file :file:`Widget/ChartWidget` is used, which provides necessary HTML to render the chart.
|
||||
The provided ``eventData`` will be rendered as a chart and therefore has to match the expected structure.
|
||||
|
||||
An example would be :file:`Classes/Widgets/BarChartWidget.php`:
|
||||
|
||||
.. code-block:: php
|
||||
:caption: Classes/Widgets/BarChartWidget.php
|
||||
|
||||
class BarChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface
|
||||
{
|
||||
public function __construct(
|
||||
// …
|
||||
private readonly ChartDataProviderInterface $dataProvider,
|
||||
// …
|
||||
) {
|
||||
// …
|
||||
$this->dataProvider = $dataProvider;
|
||||
// …
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
// …
|
||||
$this->view->assignMultiple([
|
||||
// …
|
||||
'configuration' => $this->configuration,
|
||||
// …
|
||||
]);
|
||||
// …
|
||||
}
|
||||
|
||||
public function getEventData(): array
|
||||
{
|
||||
return [
|
||||
'graphConfig' => [
|
||||
'type' => 'bar',
|
||||
'options' => [
|
||||
// …
|
||||
],
|
||||
'data' => $this->dataProvider->getChartData(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
|
||||
Together with :file:`Services.yaml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
services:
|
||||
dashboard.widget.sysLogErrors:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget'
|
||||
arguments:
|
||||
# …
|
||||
$dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider'
|
||||
# …
|
||||
tags:
|
||||
- name: dashboard.widget
|
||||
|
||||
The configuration adds necessary CSS classes, as well as the ``dataProvider`` to use.
|
||||
The provider implements :php:`ChartDataProviderInterface` and could look like the following.
|
||||
|
||||
.. code-block:: php
|
||||
:caption: Classes/Widgets/Provider/SysLogErrorsDataProvider
|
||||
|
||||
class SysLogErrorsDataProvider implements ChartDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Number of days to gather information for.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $days = 31;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $labels = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
public function __construct(int $days = 31)
|
||||
{
|
||||
$this->days = $days;
|
||||
}
|
||||
|
||||
public function getChartData(): array
|
||||
{
|
||||
$this->calculateDataForLastDays();
|
||||
return [
|
||||
'labels' => $this->labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'),
|
||||
'backgroundColor' => WidgetApi::getDefaultChartColors()[0],
|
||||
'border' => 0,
|
||||
'data' => $this->data
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNumberOfErrorsInPeriod(int $start, int $end): int
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
|
||||
return (int)$queryBuilder
|
||||
->count('*')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'type',
|
||||
$queryBuilder->createNamedParameter(SystemLogType::ERROR, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->gte(
|
||||
'tstamp',
|
||||
$queryBuilder->createNamedParameter($start, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->lte(
|
||||
'tstamp',
|
||||
$queryBuilder->createNamedParameter($end, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
}
|
||||
|
||||
protected function calculateDataForLastDays(): void
|
||||
{
|
||||
$format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d';
|
||||
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');
|
||||
$this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _for-developer:
|
||||
|
||||
==============
|
||||
For Developers
|
||||
==============
|
||||
|
||||
Target group: **Developers**
|
||||
|
||||
Welcome to our small dashboard introduction.
|
||||
We will explain how to create widget groups and implement widgets.
|
||||
|
||||
.. include:: /Shared/DifferenceRegistrationAndImplementation.rst.txt
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:titlesonly:
|
||||
|
||||
WidgetImplementation
|
||||
ConfigurableWidgets
|
||||
MakeRefreshable
|
||||
AddingButtons
|
||||
GraphWidgetImplementation
|
||||
Interfaces
|
||||
@@ -0,0 +1,178 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _interfaces:
|
||||
|
||||
==========
|
||||
Interfaces
|
||||
==========
|
||||
|
||||
The following list provides information for all necessary interfaces that are used inside of this documentation.
|
||||
For up to date information, please check the source code.
|
||||
|
||||
.. php:namespace:: TYPO3\CMS\Dashboard\Widgets
|
||||
|
||||
.. php:class:: WidgetInterface
|
||||
|
||||
Has to be implemented by all widgets.
|
||||
This interface defines public API used by ext:dashboard to interact with widgets.
|
||||
|
||||
.. php:method:: renderWidgetContent()
|
||||
|
||||
:returntype: string
|
||||
:returns: The rendered HTML to display.
|
||||
|
||||
.. php:method:: getOptions()
|
||||
|
||||
:returntype: array
|
||||
:returns: The options of the widget as set in the registration.
|
||||
|
||||
.. php:class:: RequestAwareWidgetInterface
|
||||
|
||||
This interface declares a widget has a dependency to the current PSR-7 request.
|
||||
When implemented, the dashboard controller will call :php:`setRequest()` immediately
|
||||
after widget instantiation to hand over the current request. Widgets that rely on
|
||||
:php:`BackendViewFactory` typically need the current request.
|
||||
|
||||
.. php:method:: setRequest(ServerRequestInterface $request)
|
||||
|
||||
:returntype: void
|
||||
|
||||
.. php:class:: WidgetConfigurationInterface
|
||||
|
||||
Used internally in ext:dashboard.
|
||||
Used to separate internal configuration from widgets.
|
||||
Can be required in widget classes and passed to view.
|
||||
|
||||
.. php:method:: getIdentifier()
|
||||
|
||||
:returntype: string
|
||||
:returns: Unique identifer of a widget.
|
||||
|
||||
.. php:method:: getServiceName()
|
||||
|
||||
:returntype: string
|
||||
:returns: Service name providing the widget implementation.
|
||||
|
||||
.. php:method:: getGroupNames()
|
||||
|
||||
:returntype: array
|
||||
:returns: Group names associated to this widget.
|
||||
|
||||
.. php:method:: getTitle()
|
||||
|
||||
:returntype: string
|
||||
:returns: Title of a widget, this is used for the widget selector.
|
||||
|
||||
.. php:method:: getDescription()
|
||||
|
||||
:returntype: string
|
||||
:returns: Description of a widget, this is used for the widget selector.
|
||||
|
||||
.. php:method:: getIconIdentifier()
|
||||
|
||||
:returntype: string
|
||||
:returns: Icon identifier of a widget, this is used for the widget selector.
|
||||
|
||||
.. php:method:: getHeight()
|
||||
|
||||
:returntype: int
|
||||
:returns: Height of a widget in rows (1-6).
|
||||
|
||||
.. php:method:: getWidth()
|
||||
|
||||
:returntype: int
|
||||
:returns: Width of a widget in columns (1-4).
|
||||
|
||||
.. php:class:: AdditionalJavaScriptInterface
|
||||
|
||||
Widgets implementing this interface will add the provided JavaScript files.
|
||||
Those files will be loaded in dashboard view if the widget is added at least once.
|
||||
|
||||
.. php:method:: getJsFiles()
|
||||
|
||||
Returns a list of JavaScript file names that should be included, e.g.:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
return [
|
||||
'EXT:my_extension/Resources/Public/JavaScript/file.js',
|
||||
'EXT:my_extension/Resources/Public/JavaScript/file2.js',
|
||||
];
|
||||
|
||||
:returntype: array
|
||||
:returns: List of JS files to load.
|
||||
|
||||
.. php:class:: AdditionalCssInterface
|
||||
|
||||
Widgets implementing this interface will add the provided Css files.
|
||||
Those files will be loaded in dashboard view if the widget is added at least once.
|
||||
|
||||
.. php:method:: getCssFiles()
|
||||
|
||||
Returns a list of Css file names that should be included, e.g.:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
return [
|
||||
'EXT:my_extension/Resources/Public/Css/widgets.css',
|
||||
'EXT:my_extension/Resources/Public/Css/list-widget.css',
|
||||
];
|
||||
|
||||
:returntype: array
|
||||
:returns: List of Css files to load.
|
||||
|
||||
.. php:class:: ButtonProviderInterface
|
||||
|
||||
.. php:method:: getTitle()
|
||||
|
||||
:returntype: string
|
||||
:returns: The title used for the button. E.g. an ``LLL:EXT:`` reference like
|
||||
``LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems``.
|
||||
|
||||
.. php:method:: getLink()
|
||||
|
||||
:returntype: string
|
||||
:returns: The link to use for the button. Clicking the button will open the link.
|
||||
|
||||
.. php:method:: getTarget()
|
||||
|
||||
:returntype: string
|
||||
:returns: The target of the link, e.g. ``_blank``.
|
||||
``LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems``.
|
||||
|
||||
.. php:class:: NumberWithIconDataProviderInterface
|
||||
|
||||
.. php:method:: getNumber()
|
||||
|
||||
:returntype: integer
|
||||
:returns: The number to display for an number widget.
|
||||
|
||||
.. php:class:: EventDataInterface
|
||||
|
||||
.. php:method:: getEventData()
|
||||
|
||||
:returntype: array
|
||||
:returns: Returns data which should be send to the widget as JSON encoded value.
|
||||
|
||||
.. php:class:: ChartDataProviderInterface
|
||||
|
||||
.. php:method:: getChartData()
|
||||
|
||||
:returntype: array
|
||||
:returns: Provide the data for a 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:
|
||||
|
||||
Bar
|
||||
https://www.chartjs.org/docs/latest/charts/bar.html#data-structure
|
||||
|
||||
Doughnut
|
||||
https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure
|
||||
|
||||
.. php:class:: ListDataProviderInterface
|
||||
|
||||
.. php:method:: getItems()
|
||||
|
||||
:returntype: array
|
||||
:returns: Provide the array if items.
|
||||
Each entry should be a single string.
|
||||
@@ -0,0 +1,83 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
.. _make-refreshable:
|
||||
|
||||
==================
|
||||
The refresh option
|
||||
==================
|
||||
|
||||
In each widget the refresh option can be enabled. If the option is enabled the
|
||||
widget displays a reload button in the top right corner. It can then be
|
||||
refreshed via user interaction or via a javascript api.
|
||||
|
||||
To enable the refresh action button, you have to define the
|
||||
:yaml:`refreshAvailable` option in the :yaml:`$options` part of the widget
|
||||
registration. Below is an example of a RSS widget with the refresh option enabled.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
dashboard.widget.myOwnRSSWidget:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
|
||||
arguments:
|
||||
$options:
|
||||
rssFile: 'https://typo3.org/rss'
|
||||
lifeTime: 43200
|
||||
refreshAvailable: true
|
||||
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: 'medium'
|
||||
width: 'medium'
|
||||
|
||||
.. note::
|
||||
|
||||
In this example, the TYPO3 core :php:`TYPO3\CMS\Dashboard\Widgets\RssWidget`
|
||||
widget class is used. In case you have implemented own widget classes, you
|
||||
have to implement the :php:`getOptions()` method, returning :php:`$this->options`,
|
||||
to the corresponding classes. Otherwise the refresh option won't have any
|
||||
effect.
|
||||
|
||||
.. _refresh-button:
|
||||
|
||||
Enable the refresh button
|
||||
-------------------------
|
||||
|
||||
Widgets can render a refresh button to allow users to manually refresh them.
|
||||
|
||||
This is done by passing the value :php:`['refreshAvailable'] = true;` back
|
||||
via :php:`getOptions()` method of the widget.
|
||||
|
||||
All TYPO3 Core widgets implement this behaviour and allow integrators to
|
||||
configure the option:
|
||||
|
||||
.. include:: /Widgets/Options/RefreshAvailable.rst.txt
|
||||
|
||||
.. _refresh-javascript:
|
||||
|
||||
JavaScript API
|
||||
--------------
|
||||
|
||||
It is possible for all widgets to dispatch an event, which will cause
|
||||
the widget being refreshed. This is possible for all widgets on the dashboard
|
||||
even when the :yaml:`refreshAvailable` option is not defined, or set to `false`.
|
||||
This will give developers the option to refresh the widgets whenever they think
|
||||
it is appropriate.
|
||||
|
||||
To refresh a widget, dispatch the :js:`widgetRefresh` event on the
|
||||
widget container (the :html:`div` element with the :html:`dashboard-item` class).
|
||||
You can identify the container by the data attribute :html:`widget-hash`, which
|
||||
is a unique hash for every widget, even if you have more widgets of the same
|
||||
type on your dashboard.
|
||||
|
||||
A small example below:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
document
|
||||
.querySelector('[data-widget-hash="{your-unique-widget-hash}"]')
|
||||
.dispatchEvent(new Event('widgetRefresh', {bubbles: true}));
|
||||
|
||||
See :ref:`implement-new-widget-custom-js` to learn how to add custom JavaScript.
|
||||
@@ -0,0 +1,195 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _implement-new-widget:
|
||||
|
||||
====================
|
||||
Implement new widget
|
||||
====================
|
||||
|
||||
.. versionadded:: 14.0
|
||||
`Configurable Dashboard Widgets <https://docs.typo3.org/permalink/changelog:feature-107036-1738837673>`_
|
||||
have been introduced with TYPO3 14.0.
|
||||
|
||||
See also
|
||||
|
||||
.. php:namespace:: TYPO3\CMS\Dashboard\Widgets
|
||||
|
||||
.. seealso::
|
||||
|
||||
For information regarding registration of widgets, see: :ref:`register-new-widget`.
|
||||
This section describes the implementation of new widgets for developers.
|
||||
|
||||
Each extension can provide multiple Widgets.
|
||||
ext:dashboard already ships with some widget implementations.
|
||||
|
||||
Each widget has to be implemented as a PHP class.
|
||||
The PHP class defines the concrete implementation and features of a widget,
|
||||
while registration adds necessary options for a concrete instance of a widget.
|
||||
|
||||
For example a TYPO3.org RSS Widget would consist of an :php:`RssWidget` PHP class.
|
||||
This class would provide the implementation to fetch rss news and display them.
|
||||
The concrete registration will provide the URL to RSS feed.
|
||||
|
||||
.. _widget-php-class:
|
||||
|
||||
PHP class
|
||||
---------
|
||||
|
||||
Each Widget has to be a PHP class.
|
||||
This class has to implement the :php:`WidgetInterface` and could look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class RssWidget implements WidgetInterface, RequestAwareWidgetInterface
|
||||
{
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
private readonly Cache $cache,
|
||||
private readonly BackendViewFactory $backendViewFactory,
|
||||
private readonly ?ButtonProviderInterface $buttonProvider = null,
|
||||
private readonly array $options = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function setRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$view = $this->backendViewFactory->create($this->request);
|
||||
$view->assignMultiple([
|
||||
'items' => $this->getRssItems(),
|
||||
'options' => $this->options,
|
||||
'button' => $this->getButton(),
|
||||
'configuration' => $this->configuration,
|
||||
]);
|
||||
return $view->render('Widget/RssWidget');
|
||||
}
|
||||
|
||||
protected function getRssItems(): array
|
||||
{
|
||||
$items = [];
|
||||
// Logic to populate $items array
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
|
||||
The class should always provide documentation how to use in :file:`Services.yaml`.
|
||||
The above class is documented at :ref:`rss-widget`.
|
||||
The documentation should provide all possible options and an concrete example.
|
||||
It should make it possible for integrators to register new widgets using the implementation.
|
||||
|
||||
The difference between :php:`$options` and :php:`$configuration` in above example is the following:
|
||||
:php:`$options` are the options for this implementation which can be provided through :file:`Services.yaml`.
|
||||
:php:`$configuration` is an instance of :php:`WidgetConfigurationInterface`
|
||||
holding all internal configuration, like icon identifier.
|
||||
|
||||
.. _implement-new-widget-fluid:
|
||||
|
||||
Using Fluid
|
||||
-----------
|
||||
|
||||
Most widgets will need a template.
|
||||
Therefore each widget can define :php:`BackendViewFactory` as requirement for DI in
|
||||
constructor, like done in RSS example.
|
||||
|
||||
|
||||
.. _implement-new-widget-custom-js:
|
||||
|
||||
Providing custom JS
|
||||
-------------------
|
||||
|
||||
There are two ways to add JavaScript for an widget:
|
||||
|
||||
JavaScript module
|
||||
Implement :php:`\TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
class ExampleChartWidget implements JavaScriptInterface
|
||||
{
|
||||
// ...
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return [
|
||||
JavaScriptModuleInstruction::create(
|
||||
'@myvendor/my-extension/module-name.js'
|
||||
)->invoke('initialize'),
|
||||
JavaScriptModuleInstruction::create(
|
||||
'@myvendor/my-extension/module-name2.js'
|
||||
)->invoke('initialize'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`t3coreapi:backend-javascript-es6` for more info about JavaScript in TYPO3 Backend.
|
||||
|
||||
Plain JS files
|
||||
Implement :php:`AdditionalJavaScriptInterface`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class RssWidget implements WidgetInterface, AdditionalJavaScriptInterface
|
||||
{
|
||||
public function getJsFiles(): array
|
||||
{
|
||||
return [
|
||||
'EXT:my_extension/Resources/Public/JavaScript/file.js',
|
||||
'EXT:my_extension/Resources/Public/JavaScript/file2.js',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
JavaScript
|
||||
Implement :php:`\TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class ExampleChartWidget implements JavaScriptInterface
|
||||
{
|
||||
// ...
|
||||
public function getJavaScriptModuleInstructions(): array
|
||||
{
|
||||
return [
|
||||
JavaScriptModuleInstruction::create(
|
||||
'@typo3/dashboard/chart-initializer.js'
|
||||
)->invoke('initialize'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
All ways can be combined.
|
||||
|
||||
.. _custom-css:
|
||||
|
||||
Providing custom CSS
|
||||
--------------------
|
||||
|
||||
It is possible to add custom Css to style widgets.
|
||||
|
||||
Implement :php:`AdditionalCssInterface`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class RssWidget implements WidgetInterface, AdditionalCssInterface
|
||||
{
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [
|
||||
'EXT:my_extension/Resources/Public/Css/widgets.css',
|
||||
'EXT:my_extension/Resources/Public/Css/list-widget.css',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetContext;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetRendererInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetResult;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
|
||||
class ConfigurableWidget implements WidgetRendererInterface
|
||||
{
|
||||
public function getSettingsDefinitions(): array
|
||||
{
|
||||
return [
|
||||
new SettingDefinition(
|
||||
key: 'title',
|
||||
type: 'string',
|
||||
default: 'Default Title',
|
||||
label: 'LLL:EXT:my_extension/Resources/Private/Language/locallang_my_widget.xlf:settings.label',
|
||||
description: 'LLL:EXT:my_extension/Resources/Private/Language/locallang_my_widget.xlf:settings.description.label',
|
||||
),
|
||||
new SettingDefinition(
|
||||
key: 'limit',
|
||||
type: 'int',
|
||||
default: 10,
|
||||
label: 'LLL:EXT:my_extension/Resources/Private/Language/locallang_my_widget.xlf:settings.limit',
|
||||
description: 'LLL:EXT:my_extension/Resources/Private/Language/locallang_my_widget.xlf:settings.description.limit',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function renderWidget(WidgetContext $context): WidgetResult
|
||||
{
|
||||
$settings = $context->settings;
|
||||
$title = $settings->get('title');
|
||||
$limit = $settings->get('limit');
|
||||
|
||||
// Use settings to customize widget output
|
||||
return new WidgetResult(
|
||||
content: '<!-- widget content -->',
|
||||
label: $title,
|
||||
refreshable: true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _for-editors:
|
||||
|
||||
===========
|
||||
For Editors
|
||||
===========
|
||||
|
||||
Target group: **Editors**
|
||||
|
||||
Welcome to our small dashboard introduction.
|
||||
We will explain the basic usage of the TYPO3 dashboard.
|
||||
|
||||
.. _opening-dashboard:
|
||||
|
||||
Opening Dashboard
|
||||
=================
|
||||
|
||||
By default the dashboard is opened when logging into the backend.
|
||||
|
||||
The dashboard can be opened at any time by clicking the entry
|
||||
:guilabel:`Dashboard` in the module menu.
|
||||
|
||||
.. figure:: /Images/DashboardPosition.png
|
||||
:align: center
|
||||
|
||||
Open the dashboard by clicking on :guilabel:`Dashboard`.
|
||||
|
||||
.. note::
|
||||
If the entry :guilabel:`Dashboard` is not visible in the menu there are two
|
||||
possible causes:
|
||||
|
||||
* You lack sufficient rights to view the dashboard.
|
||||
* The system extension `dashboard` was not installed on your system.
|
||||
|
||||
Ask your administrator about this.
|
||||
|
||||
.. _adding-dashboard:
|
||||
|
||||
Adding Dashboard
|
||||
================
|
||||
|
||||
The EXT:dashboard allows to have multiple dashboards.
|
||||
Switching between different dashboards is possible by using the corresponding tab.
|
||||
|
||||
In order to add further dashboards, press the :guilabel:`+` sign.
|
||||
|
||||
.. figure:: /Images/DashboardTabs.png
|
||||
:align: center
|
||||
|
||||
Tabs allowing to switch and add dashboards.
|
||||
|
||||
A wizard should open which allows to add the new dashboard.
|
||||
|
||||
There you can select a preset. At least the default preset, which is shipped
|
||||
by core should be available. Depending on system configuration further dashboard
|
||||
presets might be available.
|
||||
|
||||
.. figure:: /Images/DashboardWizard.png
|
||||
:align: center
|
||||
|
||||
Wizard to add a new dashboard.
|
||||
|
||||
.. _editing-dashboard:
|
||||
|
||||
Editing Dashboard
|
||||
=================
|
||||
|
||||
Existing dashboards can be edited and deleted.
|
||||
On the right side of the tab bar are the icons which allow deletion and adjusting
|
||||
settings of the currently active dashboard.
|
||||
|
||||
.. figure:: /Images/DashboardTabs.png
|
||||
:align: center
|
||||
|
||||
Icons on the right side of the tab bar allow adjusting settings or deletion of
|
||||
the currently selected dashboard.
|
||||
|
||||
.. _adding-widgets:
|
||||
|
||||
Adding Widgets
|
||||
==============
|
||||
|
||||
Widgets can be added to a dashboard.
|
||||
Dashboards which do not contain any widget yet, offer a dialog in the middle of
|
||||
the screen, which allows to add one or more widgets to the current dashboard.
|
||||
|
||||
All dashboards allow to add further widgets in the lower right corner through the
|
||||
:guilabel:`+` Icon.
|
||||
|
||||
.. figure:: /Images/AddWidget.png
|
||||
:align: center
|
||||
|
||||
Empty dashboard with possibilities to add new widgets.
|
||||
|
||||
Once the action to add a new widget was triggered, a wizard opens which allows to
|
||||
select the widget to add.
|
||||
|
||||
Widgets are grouped in tabs and can be added by clicking on them.
|
||||
|
||||
.. figure:: /Images/WidgetWizard.png
|
||||
:align: center
|
||||
|
||||
Wizard to select a new widget that will be added to the active dashboard.
|
||||
|
||||
.. _widgets-configuration:
|
||||
|
||||
Widget configuration
|
||||
====================
|
||||
|
||||
.. versionadded:: 14.0
|
||||
`Configurable Dashboard Widgets <https://docs.typo3.org/permalink/changelog:feature-107036-1738837673>`_
|
||||
have been introduced with TYPO3 14.0.
|
||||
|
||||
* Dashboard widgets display a settings (cog) icon when they support configuration
|
||||
* Clicking the settings icon opens a modal dialog with configurable options
|
||||
* Settings are applied immediately after saving, with the widget content
|
||||
refreshing automatically
|
||||
* Each widget can be configured independently per user / per instance
|
||||
|
||||
|
||||
.. figure:: /Images/DashboardConfiguration.png
|
||||
:alt: Screenshot of the dashboard widget "RSS Feed" with the location of the settings (cog) icon
|
||||
|
||||
Click the settings (cog) icon to configure a feed
|
||||
|
||||
Extension authors can make their widgets configurable:
|
||||
`Configurable dashboard widget implementation <https://docs.typo3.org/permalink/typo3/cms-dashboard:configurable-widget-implementation>`_.
|
||||
|
||||
.. _moving-widgets:
|
||||
|
||||
Moving Widgets
|
||||
==============
|
||||
|
||||
Widgets can be moved around. Therefore a widget needs to be hovered.
|
||||
If a widget is hovered some icons appear in the upper right corner of the widget.
|
||||
|
||||
To move the widget, click and hold left mouse button on the cross icon.
|
||||
Then move to the target position.
|
||||
|
||||
.. figure:: /Images/WidgetMove.png
|
||||
:align: center
|
||||
|
||||
Widget in hover mode with additional icons in upper right corner.
|
||||
|
||||
.. _deleting-widgets:
|
||||
|
||||
Deleting Widgets
|
||||
================
|
||||
|
||||
To delete a widget, the widget needs to be hovered.
|
||||
If a widget is hovered some icons appear in the upper right corner of the widget.
|
||||
|
||||
Click the trash icon which appears to delete the widget.
|
||||
|
||||
.. figure:: /Images/WidgetMove.png
|
||||
:align: center
|
||||
|
||||
Widget in hover mode with additional icons in upper right corner.
|
||||
|
||||
In order to prevent accidentally deletion, a modal is shown to confirm deletion.
|
||||
Confirm by clicking the :guilabel:`Remove` button.
|
||||
|
||||
.. figure:: /Images/WidgetDelete.png
|
||||
:align: center
|
||||
|
||||
Modal to confirm deletion of widget.
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1 @@
|
||||
.. You can put central messages to display on all pages here
|
||||
@@ -0,0 +1,55 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _start:
|
||||
|
||||
===============
|
||||
TYPO3 Dashboard
|
||||
===============
|
||||
|
||||
:Extension key:
|
||||
dashboard
|
||||
|
||||
:Package name:
|
||||
typo3/cms-dashboard
|
||||
|
||||
:Version:
|
||||
|release|
|
||||
|
||||
:Language:
|
||||
en
|
||||
|
||||
:Author:
|
||||
TYPO3 contributors
|
||||
|
||||
:License:
|
||||
This document is published under the
|
||||
`Open Content License <https://www.openhub.net/licenses/opl>`__.
|
||||
|
||||
:Rendered:
|
||||
|today|
|
||||
|
||||
----
|
||||
|
||||
This TYPO3 backend module is used to configure and create backend widgets.
|
||||
|
||||
----
|
||||
|
||||
**Table of Contents:**
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:titlesonly:
|
||||
|
||||
Introduction/Index
|
||||
Installation/Index
|
||||
Editor/Index
|
||||
Configuration/Index
|
||||
Developer/Index
|
||||
Widgets/Index
|
||||
|
||||
.. Meta Menu
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
Sitemap
|
||||
@@ -0,0 +1,54 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _installation:
|
||||
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
Target group: **Administrators**
|
||||
|
||||
This extension is part of the TYPO3 Core, but not installed by default.
|
||||
|
||||
.. contents:: Table of contents
|
||||
:local:
|
||||
|
||||
.. _installation-composer:
|
||||
|
||||
Installation with Composer
|
||||
==========================
|
||||
|
||||
Check whether you are already using the extension with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
composer show | grep dashboard
|
||||
|
||||
This should either give you no result or something similar to:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
typo3/cms-dashboard v12.4.11
|
||||
|
||||
If it is not installed yet, use the ``composer require`` command to install
|
||||
the extension:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
composer require typo3/cms-dashboard
|
||||
|
||||
The given version depends on the version of the TYPO3 Core you are using.
|
||||
|
||||
.. _installation-no-composer:
|
||||
|
||||
Installation without Composer
|
||||
=============================
|
||||
|
||||
In an installation without Composer, the extension is already shipped. You just have to activate it.
|
||||
Head over to the extension manager and activate the extension.
|
||||
|
||||
.. figure:: /Images/InstallActivate.png
|
||||
:class: with-shadow
|
||||
:alt: Extension manager showing Dashboard extension
|
||||
|
||||
Extension manager showing Dashboard extension
|
||||
@@ -0,0 +1,16 @@
|
||||
:navigation-title: Introduction
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _introduction:
|
||||
|
||||
==============================
|
||||
Introduction: What does it do?
|
||||
==============================
|
||||
|
||||
This extension provides a new TYPO3 backend module "Dashboard".
|
||||
Users can create multiple dashboards visible in this module, and switch between those
|
||||
dashboards.
|
||||
Each of the dashboards can have multiple widgets.
|
||||
|
||||
Developers are able to create new widgets.
|
||||
Integrators and developers are able to register new widgets through configuration.
|
||||
@@ -0,0 +1,13 @@
|
||||
.. note::
|
||||
|
||||
Difference between **registration** of widgets and **implementation** of widgets:
|
||||
|
||||
Widgets provide some functionality, e.g. collect system log errors over a time span.
|
||||
This functionality is provided by the implementation, a PHP class with some code.
|
||||
The registration is done in :file:`Services.yaml`,
|
||||
in order to create the actual widget with provided functionality.
|
||||
During registration options can be set, e.g. the time span.
|
||||
|
||||
Registration is documented at :ref:`register-new-widget`,
|
||||
while implementation is documented at
|
||||
:ref:`implement-new-widget`.
|
||||
@@ -0,0 +1,11 @@
|
||||
:template: sitemap.html
|
||||
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _sitemap:
|
||||
|
||||
=======
|
||||
Sitemap
|
||||
=======
|
||||
|
||||
.. The sitemap.html template will insert here the page tree automatically.
|
||||
@@ -0,0 +1,73 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _bar-chart-widget:
|
||||
|
||||
================
|
||||
Bar Chart Widget
|
||||
================
|
||||
|
||||
.. php:namespace:: TYPO3\CMS\Dashboard\Widgets
|
||||
|
||||
Widgets using this class will show a bar chart with the provided data.
|
||||
|
||||
This kind of widgets are useful if you want to show some statistics of for example
|
||||
historical data.
|
||||
|
||||
.. php:class:: TYPO3\CMS\Dashboard\Widgets\BarChartWidget
|
||||
|
||||
.. _bar-chart-widget-example:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: Excerpt from EXT:dashboard/Configuration/Services.yaml
|
||||
|
||||
services:
|
||||
dashboard.widget.sysLogErrors:
|
||||
class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget'
|
||||
arguments:
|
||||
$dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider'
|
||||
$buttonProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider'
|
||||
$options:
|
||||
refreshAvailable: true
|
||||
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'
|
||||
height: 'medium'
|
||||
width: 'medium'
|
||||
|
||||
.. _bar-chart-widget-options:
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. include:: Options/RefreshAvailable.rst.txt
|
||||
|
||||
.. _bar-chart-widget-dependencies:
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
.. confval:: $dataProvider
|
||||
:type: :php:`\TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface`
|
||||
:name: bar-chart-widget-dataProvider
|
||||
|
||||
To add data to a Bar Chart widget, you need to have a DataProvider that implements
|
||||
the interface :php-short:`\TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface`.
|
||||
|
||||
See :ref:`graph-widget-implementation` for further information.
|
||||
|
||||
.. confval:: $buttonProvider
|
||||
:type: :php:`\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface`
|
||||
:name: bar-chart-widget-buttonProvider
|
||||
|
||||
Optionally you can add a button with a link to some additional data.
|
||||
This button should be provided by a ButtonProvider that implements the interface
|
||||
:php-short:`\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface`.
|
||||
|
||||
See :ref:`adding-buttons` for further info and configuration options.
|
||||