TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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,
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user