TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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