first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,288 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Reports\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Reports\ReportInterface;
use TYPO3\CMS\Reports\RequestAwareReportInterface;
use TYPO3Fluid\Fluid\View\ViewInterface;
/**
* Reports controller
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class ReportController
{
use PublicMethodDeprecationTrait;
/**
* @var array
*/
private $deprecatedPublicMethods = [
'indexAction' => 'Using ReportController::indexAction() is deprecated and will not be possible anymore in TYPO3 v10.0.',
'detailAction' => 'Using ReportController::detailAction() is deprecated and will not be possible anymore in TYPO3 v10.0.',
];
/**
* ModuleTemplate object
*
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* @var ViewInterface
*/
protected $view;
/**
* Module name for the shortcut
*
* @var string
*/
protected $shortcutName;
/**
* Instantiate the report controller
*/
public function __construct()
{
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
}
/**
* Injects the request object for the current request, and renders correct action
*
* @param ServerRequestInterface $request the current request
* @return ResponseInterface the response with the content
*/
public function handleRequest(ServerRequestInterface $request): ResponseInterface
{
$action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? '';
$extension = $request->getQueryParams()['extension'] ?? $request->getParsedBody()['extension'];
$isRedirect = $request->getQueryParams()['redirect'] ?? $request->getParsedBody()['redirect'] ?? false;
if ($action !== 'index' && !$isRedirect && !$extension
&& is_array($GLOBALS['BE_USER']->uc['reports']['selection'])) {
$previousSelection = $GLOBALS['BE_USER']->uc['reports']['selection'];
if (!empty($previousSelection['extension']) && !empty($previousSelection['report'])) {
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
return new RedirectResponse((string)$uriBuilder->buildUriFromRoute('system_reports', [
'action' => 'detail',
'extension' => $previousSelection['extension'],
'report' => $previousSelection['report'],
'redirect' => 1,
]), 303);
}
}
if (empty($action)) {
$action = 'index';
}
$this->initializeView($action);
if ($action === 'index') {
$this->indexAction();
} elseif ($action === 'detail') {
$response = $this->detailAction($request);
if ($response instanceof ResponseInterface) {
return $response;
}
} else {
throw new \RuntimeException(
'Reports module has only "index" and "detail" action, ' . (string)$action . ' given',
1536322935
);
}
$this->generateMenu($request);
$this->generateButtons();
$this->moduleTemplate->setContent($this->view->render());
return new HtmlResponse($this->moduleTemplate->renderContent());
}
/**
* @param string $templateName
*/
protected function initializeView(string $templateName)
{
$this->view = GeneralUtility::makeInstance(StandaloneView::class);
$this->view->setTemplate($templateName);
$this->view->setTemplateRootPaths(['EXT:reports/Resources/Private/Templates/Report']);
$this->view->setPartialRootPaths(['EXT:reports/Resources/Private/Partials']);
$this->view->setLayoutRootPaths(['EXT:reports/Resources/Private/Layouts']);
$this->view->getRequest()->setControllerExtensionName('Reports');
}
/**
* Overview
*/
protected function indexAction()
{
$this->view->assign('reports', $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']);
$this->saveState();
}
/**
* Display a single report
*
* @param ServerRequestInterface $request
* @return ResponseInterface|null
*/
protected function detailAction(ServerRequestInterface $request)
{
$content = $error = '';
$extension = $request->getQueryParams()['extension'] ?? $request->getParsedBody()['extension'];
$report = $request->getQueryParams()['report'] ?? $request->getParsedBody()['report'];
$reportClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports'][$extension][$report]['report'] ?? null;
if ($reportClass === null || !class_exists($reportClass)) {
$this->resetState();
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
return new RedirectResponse((string)$uriBuilder->buildUriFromRoute('system_reports', [
'action' => 'index',
'redirect' => 1,
]), 303);
}
$reportInstance = GeneralUtility::makeInstance($reportClass, $this);
if ($reportInstance instanceof ReportInterface) {
if ($reportInstance instanceof RequestAwareReportInterface) {
$content = $reportInstance->getReport($request);
} else {
$content = $reportInstance->getReport();
}
$this->saveState($extension, $report);
} else {
$error = $reportClass . ' does not implement the Report Interface which is necessary to be displayed here.';
}
$this->view->assignMultiple([
'content' => $content,
'error' => $error,
'report' => $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports'][$extension][$report],
]);
}
/**
* Generates the menu
*
* @param ServerRequestInterface $request
*/
protected function generateMenu(ServerRequestInterface $request)
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$lang = $this->getLanguageService();
$lang->includeLLFile('EXT:reports/Resources/Private/Language/locallang.xlf');
$menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
$menu->setIdentifier('WebFuncJumpMenu');
$menuItem = $menu
->makeMenuItem()
->setHref(
$uriBuilder->buildUriFromRoute('system_reports', ['action' => 'index'])
)
->setTitle($lang->getLL('reports_overview'));
$menu->addMenuItem($menuItem);
$this->shortcutName = $lang->getLL('reports_overview');
$extensionParam = $request->getQueryParams()['extension'] ?? $request->getParsedBody()['extension'];
$reportParam = $request->getQueryParams()['report'] ?? $request->getParsedBody()['report'];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports'] as $extKey => $reports) {
foreach ($reports as $reportName => $report) {
$menuItem = $menu
->makeMenuItem()
->setHref($uriBuilder->buildUriFromRoute(
'system_reports',
['action' => 'detail', 'extension' => $extKey, 'report' => $reportName]
))
->setTitle($this->getLanguageService()->sL($report['title']));
if ($extensionParam === $extKey && $reportParam === $reportName) {
$menuItem->setActive(true);
$this->shortcutName = $menuItem->getTitle();
}
$menu->addMenuItem($menuItem);
}
}
$this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
}
/**
* Gets all buttons for the docHeader
*/
protected function generateButtons()
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
$shortcutButton = $buttonBar->makeShortcutButton()
->setModuleName('system_reports')
->setGetVariables(['action', 'extension', 'report'])
->setDisplayName($this->shortcutName);
$buttonBar->addButton($shortcutButton);
}
/**
* Save the selected report
*
* @param string $extension Extension name
* @param string $report Report name
*/
protected function saveState(string $extension = '', string $report = '')
{
$this->getBackendUser()->uc['reports']['selection'] = [
'extension' => $extension,
'report' => $report,
];
$this->getBackendUser()->writeUC();
}
/**
* Reset state in user settings
*/
protected function resetState(): void
{
$this->getBackendUser()->uc['reports']['selection'] = [];
$this->getBackendUser()->writeUC();
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,28 @@
<?php
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Interface for classes which provide a status report entry.
*/
interface ExtendedStatusProviderInterface
{
/**
* Returns the detailed status of an extension or (sub)system
*
* @return \TYPO3\CMS\Reports\Status[]
*/
public function getDetailedStatus();
}
@@ -0,0 +1,239 @@
<?php
namespace TYPO3\CMS\Reports\Report;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Exception;
use TYPO3\CMS\Core\Utility\CommandUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Reports\Controller\ReportController;
use TYPO3\CMS\Reports\ReportInterface;
/**
* This class provides a report displaying a list of all installed services
*/
class ServicesListReport implements ReportInterface
{
/**
* @var ReportController
*/
protected $reportsModule;
/**
* Constructor
*
* @param ReportController $reportsModule Back-reference to the calling reports module
*/
public function __construct(ReportController $reportsModule)
{
$this->reportsModule = $reportsModule;
$this->getLanguageService()->includeLLFile('EXT:reports/Resources/Private/Language/locallang_servicereport.xlf');
}
/**
* This method renders the report
*
* @return string The status report as HTML
*/
public function getReport()
{
// Rendering of the output via fluid
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->getRequest()->setControllerExtensionName('Reports');
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
'EXT:reports/Resources/Private/Templates/ServicesListReport.html'
));
$view->assignMultiple([
'servicesList' => $this->getServicesList(),
'searchPaths' => $this->getExecutablesSearchPathList()
]);
return $view->render();
}
/**
* This method assembles a list of all installed services
*
* @return array with data to display
*/
protected function getServicesList()
{
$servicesList = [];
$services = $this->getInstalledServices();
foreach ($services as $serviceType => $installedServices) {
$servicesList[] = $this->getServiceTypeList($serviceType, $installedServices);
}
return $servicesList;
}
/**
* Get the services list for a single service type.
*
* @param string $serviceType The service type to render the installed services list for
* @param array $services List of services for the given type
* @return array Service list as array for one service type
*/
protected function getServiceTypeList($serviceType, $services)
{
$lang = $this->getLanguageService();
$serviceList = [];
$serviceList['Type'] = sprintf($lang->getLL('service_type'), $serviceType);
$serviceList['Services'] = [];
foreach ($services as $serviceKey => $serviceInformation) {
$serviceList['Services'][] = $this->getServiceRow($serviceKey, $serviceInformation);
}
return $serviceList;
}
/**
* Get data of a single service's row.
*
* @param string $serviceKey The service key to access the service.
* @param array $serviceInformation registration information of the service.
* @return array data for one row for the service.
*/
protected function getServiceRow($serviceKey, $serviceInformation)
{
$result = [
'Key' => $serviceKey,
'Information' => $serviceInformation,
'Subtypes' => $serviceInformation['serviceSubTypes'] ? implode(', ', $serviceInformation['serviceSubTypes']) : '-',
'OperatingSystem' => $serviceInformation['os'] ?: $this->getLanguageService()->getLL('any'),
'RequiredExecutables' => $serviceInformation['exec'] ?: '-',
'AvailabilityClass' => 'danger',
'Available' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no'),
];
try {
$serviceDetails = ExtensionManagementUtility::findServiceByKey($serviceKey);
if ($serviceDetails['available']) {
$result['AvailabilityClass'] = 'success';
$result['Available'] = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes');
}
} catch (Exception $e) {
}
return $result;
}
/**
* This method assembles a list of all defined executables search paths
*
* @return array data to display
*/
protected function getExecutablesSearchPathList()
{
$searchPaths = CommandUtility::getPaths(true);
$result = [];
foreach ($searchPaths as $path => $isValid) {
$searchPathData = $this->getServicePathStatus($isValid);
$result[] = [
'class' => $searchPathData['statusCSSClass'],
'accessible' => 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:' . $searchPathData['accessible'],
'path' => GeneralUtility::fixWindowsFilePath($path),
];
}
return $result;
}
/**
* This method filters the $T3_SERVICES global array to return a relevant,
* ordered list of installed services.
*
* Every installed service appears twice in $T3_SERVICES: once as a service key
* for a given service type, and once a service type all by itself
* The list of services to display must avoid these duplicates
*
* Furthermore, inside each service type, installed services must be
* ordered by priority and quality
*
* @return array List of filtered and ordered services
*/
protected function getInstalledServices()
{
$filteredServices = [];
foreach ($GLOBALS['T3_SERVICES'] as $serviceType => $serviceList) {
// If the (first) key of the service list is not the same as the service type,
// it's a "true" service type. Keep it and sort it.
if (key($serviceList) !== $serviceType) {
uasort($serviceList, [$this, 'sortServices']);
$filteredServices[$serviceType] = $serviceList;
}
}
return $filteredServices;
}
/**
* Utility method used to sort services according to their priority and
* quality.
*
* @param array $a First service to compare
* @param array $b Second service to compare
* @return int 1, 0 or -1 if $a is smaller, equal or greater than $b, respectively
*/
protected function sortServices(array $a, array $b)
{
$result = 0;
// If priorities are the same, test quality
if ($a['priority'] === $b['priority']) {
if ($a['quality'] !== $b['quality']) {
// Service with highest quality should come first,
// thus it must be marked as smaller
$result = $a['quality'] > $b['quality'] ? -1 : 1;
}
} else {
// Service with highest priority should come first,
// thus it must be marked as smaller
$result = $a['priority'] > $b['priority'] ? -1 : 1;
}
return $result;
}
/**
* Method to check if the service in path is available
* @param bool|string $isValid
* @return array
*/
private function getServicePathStatus($isValid): array
{
$statusCSSClass = 'danger';
$accessible = 'no';
if ($isValid) {
$statusCSSClass = 'success';
$accessible = 'yes';
}
return [
'statusCSSClass' => $statusCSSClass,
'accessible' => $accessible
];
}
/**
* Returns LanguageService
*
* @return \TYPO3\CMS\Core\Localization\LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,344 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Performs some checks about the install tool protection status
*/
class ConfigurationStatus implements StatusProviderInterface
{
/**
* Determines the Install Tool's status, mainly concerning its protection.
*
* @return array List of statuses
*/
public function getStatus()
{
$statuses = [
'emptyReferenceIndex' => $this->getReferenceIndexStatus(),
];
if ($this->isMemcachedUsed()) {
$statuses['memcachedConnection'] = $this->getMemcachedConnectionStatus();
}
if (!Environment::isWindows()) {
$statuses['createdFilesWorldWritable'] = $this->getCreatedFilesWorldWritableStatus();
$statuses['createdDirectoriesWorldWritable'] = $this->getCreatedDirectoriesWorldWritableStatus();
}
if ($this->isMysqlUsed()) {
$statuses['mysqlDatabaseUsesUtf8'] = $this->getMysqlDatabaseUtf8Status();
}
return $statuses;
}
/**
* Checks if sys_refindex is empty.
*
* @return \TYPO3\CMS\Reports\Status An object representing whether the reference index is empty or not
*/
protected function getReferenceIndexStatus()
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
$count = $queryBuilder
->count('*')
->from('sys_refindex')
->execute()
->fetchColumn(0);
$registry = GeneralUtility::makeInstance(Registry::class);
$lastRefIndexUpdate = $registry->get('core', 'sys_refindex_lastUpdate');
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
if (!$count && $lastRefIndexUpdate) {
$value = $this->getLanguageService()->getLL('status_empty');
$severity = ReportStatus::WARNING;
$url = (string)$uriBuilder->buildUriFromRoute('system_dbint') . '&id=0&SET[function]=refindex';
$message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.backend_reference_index'), '<a href="' . htmlspecialchars($url) . '">', '</a>', BackendUtility::datetime($lastRefIndexUpdate));
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_referenceIndex'), $value, $message, $severity);
}
/**
* Checks whether memcached is configured, if that's the case we assume it's also used.
*
* @return bool TRUE if memcached is used, FALSE otherwise.
*/
protected function isMemcachedUsed()
{
$memcachedUsed = false;
$memcachedServers = $this->getConfiguredMemcachedServers();
if (!empty($memcachedServers)) {
$memcachedUsed = true;
}
return $memcachedUsed;
}
/**
* Gets the configured memcached server connections.
*
* @return array An array of configured memcached server connections.
*/
protected function getConfiguredMemcachedServers()
{
$configurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? [];
$memcachedServers = [];
foreach ($configurations as $table => $conf) {
if (is_array($conf)) {
foreach ($conf as $key => $value) {
if ($value === \TYPO3\CMS\Core\Cache\Backend\MemcachedBackend::class) {
$memcachedServers = $configurations[$table]['options']['servers'];
break;
}
}
}
}
return $memcachedServers;
}
/**
* Checks whether TYPO3 can connect to the configured memcached servers.
*
* @return \TYPO3\CMS\Reports\Status An object representing whether TYPO3 can connect to the configured memcached servers
*/
protected function getMemcachedConnectionStatus()
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
$failedConnections = [];
$defaultMemcachedPort = ini_get('memcache.default_port');
$memcachedServers = $this->getConfiguredMemcachedServers();
if (function_exists('memcache_connect') && is_array($memcachedServers)) {
foreach ($memcachedServers as $testServer) {
$configuredServer = $testServer;
if (strpos($testServer, 'unix://') === 0) {
$host = $testServer;
$port = 0;
} else {
if (strpos($testServer, 'tcp://') === 0) {
$testServer = substr($testServer, 6);
}
if (strstr($testServer, ':') !== false) {
list($host, $port) = explode(':', $testServer, 2);
} else {
$host = $testServer;
$port = $defaultMemcachedPort;
}
}
$memcachedConnection = @memcache_connect($host, $port);
if ($memcachedConnection != null) {
memcache_close();
} else {
$failedConnections[] = $configuredServer;
}
}
}
if (!empty($failedConnections)) {
$value = $this->getLanguageService()->getLL('status_connectionFailed');
$severity = ReportStatus::WARNING;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.memcache_not_usable') . '<br /><br />' . '<ul><li>' . implode('</li><li>', $failedConnections) . '</li></ul>';
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_memcachedConfiguration'), $value, $message, $severity);
}
/**
* Warning, if fileCreateMask has write bit for 'others' set.
*
* @return \TYPO3\CMS\Reports\Status The writable status for 'others'
*/
protected function getCreatedFilesWorldWritableStatus()
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] % 10 & 2) {
$value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
$severity = ReportStatus::WARNING;
$message = $this->getLanguageService()->getLL('status_CreatedFilePermissions.writable');
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedFilePermissions'), $value, $message, $severity);
}
/**
* Warning, if folderCreateMask has write bit for 'others' set.
*
* @return \TYPO3\CMS\Reports\Status The writable status for 'others'
*/
protected function getCreatedDirectoriesWorldWritableStatus()
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] % 10 & 2) {
$value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
$severity = ReportStatus::WARNING;
$message = $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions.writable');
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions'), $value, $message, $severity);
}
/**
* Checks if the default connection is a MySQL compatible database instance.
*
* @return bool
*/
protected function isMysqlUsed()
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
return strpos($connection->getServerVersion(), 'MySQL') === 0;
}
/**
* Checks the character set of the default database and reports an error if it is not utf-8.
*
* @return ReportStatus
*/
protected function getMysqlDatabaseUtf8Status()
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $connection->createQueryBuilder();
$defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
->from('information_schema.SCHEMATA')
->where(
$queryBuilder->expr()->eq(
'SCHEMA_NAME',
$queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
)
)
->setMaxResults(1)
->execute()
->fetchColumn();
$severity = ReportStatus::OK;
$statusValue = $this->getLanguageService()->getLL('status_ok');
// also allow utf8mb4
if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
// If the default character set is e.g. latin1, BUT all tables in the system are UTF-8,
// we assume that TYPO3 has the correct charset for adding tables, and everything is fine
$queryBuilder = $connection->createQueryBuilder();
$nonUtf8TableCollationsFound = $queryBuilder->select('table_collation')
->from('information_schema.tables')
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
$queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote('utf8%'))
)
)
->setMaxResults(1)
->execute();
if ($nonUtf8TableCollationsFound->rowCount() > 0) {
$message = sprintf($this->getLanguageService()
->getLL('status_MysqlDatabaseCharacterSet_Unsupported'), $defaultDatabaseCharset);
$severity = ReportStatus::ERROR;
$statusValue = $this->getLanguageService()->getLL('status_wrongValue');
} else {
$message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Info');
$severity = ReportStatus::INFO;
$statusValue = $this->getLanguageService()->getLL('status_info');
}
} elseif (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['tableoptions'])) {
$message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
$tableOptions = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['tableoptions'];
if (isset($tableOptions['collate'])) {
$collationConstraint = $queryBuilder->expr()->neq('table_collation', $queryBuilder->quote($tableOptions['collate']));
$charset = $tableOptions['collate'];
} elseif (isset($tableOptions['charset'])) {
$collationConstraint = $queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote($tableOptions['charset'] . '%'));
$charset = $tableOptions['charset'];
}
if (isset($collationConstraint)) {
$queryBuilder = $connection->createQueryBuilder();
$wrongCollationTablesFound = $queryBuilder->select('table_collation')
->from('information_schema.tables')
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
$collationConstraint
)
)
->setMaxResults(1)
->execute();
if ($wrongCollationTablesFound->rowCount() > 0) {
$message = sprintf($this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_MixedCollations'), $charset);
$severity = ReportStatus::ERROR;
$statusValue = $this->getLanguageService()->getLL('status_checkFailed');
} else {
if (isset($tableOptions['collate'])) {
$collationConstraint = $queryBuilder->expr()->neq('collation_name', $queryBuilder->quote($tableOptions['collate']));
} elseif (isset($tableOptions['charset'])) {
$collationConstraint = $queryBuilder->expr()->notLike('collation_name', $queryBuilder->quote($tableOptions['charset'] . '%'));
}
$queryBuilder = $connection->createQueryBuilder();
$wrongCollationColumnsFound = $queryBuilder->select('collation_name')
->from('information_schema.columns')
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
$collationConstraint
)
)
->setMaxResults(1)
->execute();
if ($wrongCollationColumnsFound->rowCount() > 0) {
$message = sprintf($this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_MixedCollations'), $charset);
$severity = ReportStatus::ERROR;
$statusValue = $this->getLanguageService()->getLL('status_checkFailed');
}
}
}
} else {
$message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
}
return GeneralUtility::makeInstance(
ReportStatus::class,
$this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet'),
$statusValue,
$message,
$severity
);
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,132 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Performs several checks about the FAL status
*/
class FalStatus implements StatusProviderInterface
{
/**
* Determines the status of the FAL index.
*
* @return array List of statuses
*/
public function getStatus()
{
$statuses = [
'MissingFiles' => $this->getMissingFilesStatus(),
];
return $statuses;
}
/**
* Checks if there are files marked as missed.
*
* @return \TYPO3\CMS\Reports\Status An object representing whether there are files marked as missed or not
*/
protected function getMissingFilesStatus()
{
$value = $this->getLanguageService()->getLL('status_none');
$count = 0;
$maxFilesToShow = 100;
$message = '';
$severity = ReportStatus::OK;
/** @var StorageRepository $storageRepository */
$storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
$storageObjects = $storageRepository->findAll();
$storages = [];
/** @var \TYPO3\CMS\Core\Resource\ResourceStorage $storageObject */
foreach ($storageObjects as $storageObject) {
// We only check missing files for storages that are online
if ($storageObject->isOnline()) {
$storages[$storageObject->getUid()] = $storageObject;
}
}
if (!empty($storages)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file');
$count = $queryBuilder
->count('*')
->from('sys_file')
->where(
$queryBuilder->expr()->eq(
'missing',
$queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
),
$queryBuilder->expr()->in(
'storage',
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
)
)
->execute()
->fetchColumn(0);
}
if ($count) {
$value = sprintf($this->getLanguageService()->getLL('status_missingFilesCount'), $count);
$severity = ReportStatus::WARNING;
$queryBuilder->resetQueryParts();
$files = $queryBuilder
->select('identifier', 'storage')
->from('sys_file')
->where(
$queryBuilder->expr()->eq(
'missing',
$queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
),
$queryBuilder->expr()->in(
'storage',
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
)
)
->setMaxResults($maxFilesToShow)
->execute()
->fetchAll();
$message = '<p>' . $this->getLanguageService()->getLL('status_missingFilesMessage') . '</p>';
foreach ($files as $file) {
$message .= $storages[$file['storage']]->getName() . ' ' . $file['identifier'] . '<br />';
}
if ($count > $maxFilesToShow) {
$message .= '...<br />';
}
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_missingFiles'), $value, $message, $severity);
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,248 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
use TYPO3\CMS\Reports\Status as ReportStatus;
/**
* Performs several checks about the system's health
*/
class SecurityStatus implements RequestAwareStatusProviderInterface
{
/**
* @var ServerRequestInterface
*/
protected $request;
/**
* Determines the security of this TYPO3 installation
*
* @param ServerRequestInterface|null $request
* @return ReportStatus[] List of statuses
*/
public function getStatus(ServerRequestInterface $request = null)
{
$statuses = [
'trustedHostsPattern' => $this->getTrustedHostsPatternStatus(),
'adminUserAccount' => $this->getAdminAccountStatus(),
'fileDenyPattern' => $this->getFileDenyPatternStatus(),
'htaccessUpload' => $this->getHtaccessUploadStatus(),
];
if ($request !== null) {
$statuses['encryptedConnectionStatus'] = $this->getEncryptedConnectionStatus($request);
$lockSslStatus = $this->getLockSslStatus($request);
if ($lockSslStatus) {
$statuses['getLockSslStatus'] = $lockSslStatus;
}
}
return $statuses;
}
/**
* Checks if the current connection is encrypted (HTTPS)
*
* @param ServerRequestInterface $request
* @return ReportStatus
*/
protected function getEncryptedConnectionStatus(ServerRequestInterface $request): ReportStatus
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
/** @var \TYPO3\CMS\Core\Http\NormalizedParams $normalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');
if (!$normalizedParams->isHttps()) {
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::WARNING;
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_encryptedConnectionStatus_insecure');
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_encryptedConnectionStatus'), $value, $message, $severity);
}
/**
* @param ServerRequestInterface $request
* @return ReportStatus
*/
protected function getLockSslStatus(ServerRequestInterface $request): ?ReportStatus
{
/** @var \TYPO3\CMS\Core\Http\NormalizedParams $normalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');
if ($normalizedParams->isHttps()) {
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL']) {
$value = $this->getLanguageService()->getLL('status_insecure');
$message = $this->getLanguageService()->getLL('status_lockSslStatus_insecure');
$severity = ReportStatus::WARNING;
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_lockSslStatus'), $value, $message, $severity);
}
return null;
}
/**
* Checks if the trusted hosts pattern check is disabled.
*
* @return ReportStatus An object representing whether the check is disabled
*/
protected function getTrustedHostsPatternStatus(): ReportStatus
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_trustedHostsPattern'), $value, $message, $severity);
}
/**
* Checks whether a BE user account named admin with default password exists.
*
* @return ReportStatus An object representing whether a default admin account exists
*/
protected function getAdminAccountStatus(): ReportStatus
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$row = $queryBuilder
->select('uid', 'username', 'password')
->from('be_users')
->where(
$queryBuilder->expr()->eq(
'username',
$queryBuilder->createNamedParameter('admin', \PDO::PARAM_STR)
)
)
->execute()
->fetch();
if (!empty($row)) {
try {
$hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->get($row['password'], 'BE');
if ($hashInstance->checkPassword('password', $row['password'])) {
// If the password for 'admin' user is 'password': bad idea!
// We're checking since the (very) old installer created instances like this in dark old times.
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::ERROR;
$editUserAccountUrl = (string)$uriBuilder->buildUriFromRoute(
'record_edit',
[
'edit[be_users][' . $row['uid'] . ']' => 'edit',
'returnUrl' => (string)$uriBuilder->buildUriFromRoute('system_reports')
]
);
$message = sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.backend_admin'),
'<a href="' . htmlspecialchars($editUserAccountUrl) . '">',
'</a>'
);
}
} catch (InvalidPasswordHashException $e) {
// No hash class handling for current hash could be found. Not good, but ok in this case.
}
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_adminUserAccount'), $value, $message, $severity);
}
/**
* Checks if fileDenyPattern was changed which is dangerous on Apache
*
* @return ReportStatus An object representing whether the file deny pattern has changed
*/
protected function getFileDenyPatternStatus(): ReportStatus
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
$defaultParts = GeneralUtility::trimExplode('|', FILE_DENY_PATTERN_DEFAULT, true);
$givenParts = GeneralUtility::trimExplode('|', $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'], true);
$result = array_intersect($defaultParts, $givenParts);
if ($defaultParts !== $result) {
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::ERROR;
$message = sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'),
'<br /><pre>' . htmlspecialchars(FILE_DENY_PATTERN_DEFAULT) . '</pre><br />'
);
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_fileDenyPattern'), $value, $message, $severity);
}
/**
* Checks if fileDenyPattern allows to upload .htaccess files which is
* dangerous on Apache.
*
* @return ReportStatus An object representing whether it's possible to upload .htaccess files
*/
protected function getHtaccessUploadStatus(): ReportStatus
{
$value = $this->getLanguageService()->getLL('status_ok');
$message = '';
$severity = ReportStatus::OK;
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] != FILE_DENY_PATTERN_DEFAULT
&& GeneralUtility::verifyFilenameAgainstDenyPattern('.htaccess')) {
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess');
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_htaccessUploadProtection'), $value, $message, $severity);
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,270 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\ExtendedStatusProviderInterface;
use TYPO3\CMS\Reports\RequestAwareReportInterface;
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* The status report
*/
class Status implements RequestAwareReportInterface
{
/**
* @var StatusProviderInterface[][]
*/
protected $statusProviders = [];
/**
* Constructor for class tx_reports_report_Status
*/
public function __construct()
{
$this->getLanguageService()->includeLLFile('EXT:reports/Resources/Private/Language/locallang_reports.xlf');
$this->getStatusProviders();
}
/**
* Takes care of creating / rendering the status report
*
* @param ServerRequestInterface|null $request the currently handled request
* @return string The status report as HTML
*/
public function getReport(ServerRequestInterface $request = null)
{
$content = '';
$status = $this->getSystemStatus($request);
$highestSeverity = $this->getHighestSeverity($status);
// Updating the registry
$registry = GeneralUtility::makeInstance(Registry::class);
$registry->set('tx_reports', 'status.highestSeverity', $highestSeverity);
$content .= '<p class="lead">' . $this->getLanguageService()->getLL('status_report_explanation') . '</p>';
return $content . $this->renderStatus($status);
}
/**
* Gets all registered status providers and creates instances of them.
*/
protected function getStatusProviders()
{
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers'] as $key => $statusProvidersList) {
$this->statusProviders[$key] = [];
foreach ($statusProvidersList as $statusProvider) {
$statusProviderInstance = GeneralUtility::makeInstance($statusProvider);
if ($statusProviderInstance instanceof StatusProviderInterface) {
$this->statusProviders[$key][] = $statusProviderInstance;
}
}
}
}
/**
* Runs through all status providers and returns all statuses collected.
*
* @param ServerRequestInterface $request
* @return \TYPO3\CMS\Reports\Status[]
*/
public function getSystemStatus(ServerRequestInterface $request = null)
{
$status = [];
foreach ($this->statusProviders as $statusProviderId => $statusProviderList) {
$status[$statusProviderId] = [];
foreach ($statusProviderList as $statusProvider) {
if ($statusProvider instanceof RequestAwareStatusProviderInterface) {
$statuses = $statusProvider->getStatus($request);
} else {
$statuses = $statusProvider->getStatus();
}
$status[$statusProviderId] = array_merge($status[$statusProviderId], $statuses);
}
}
return $status;
}
/**
* Runs through all status providers and returns all statuses collected, which are detailed.
*
* @return \TYPO3\CMS\Reports\Status[]
*/
public function getDetailedSystemStatus()
{
$status = [];
foreach ($this->statusProviders as $statusProviderId => $statusProviderList) {
$status[$statusProviderId] = [];
foreach ($statusProviderList as $statusProvider) {
if ($statusProvider instanceof ExtendedStatusProviderInterface) {
$statuses = $statusProvider->getDetailedStatus();
$status[$statusProviderId] = array_merge($status[$statusProviderId], $statuses);
}
}
}
return $status;
}
/**
* Determines the highest severity from the given statuses.
*
* @param array $statusCollection An array of \TYPO3\CMS\Reports\Status objects.
* @return int The highest severity found from the statuses.
*/
public function getHighestSeverity(array $statusCollection)
{
$highestSeverity = ReportStatus::NOTICE;
foreach ($statusCollection as $statusProvider => $providerStatuses) {
/** @var ReportStatus $status */
foreach ($providerStatuses as $status) {
if ($status->getSeverity() > $highestSeverity) {
$highestSeverity = $status->getSeverity();
}
// Reached the highest severity level, no need to go on
if ($highestSeverity == ReportStatus::ERROR) {
break;
}
}
}
return $highestSeverity;
}
/**
* Renders the system's status
*
* @param array $statusCollection An array of statuses as returned by the available status providers
* @return string The system status as an HTML table
*/
protected function renderStatus(array $statusCollection)
{
$content = '';
$template = '
<tr>
<td class="###CLASS### col-xs-6">###HEADER###</td>
<td class="###CLASS### col-xs-6">###STATUS###<br>###CONTENT###</td>
</tr>
';
$statuses = $this->sortStatusProviders($statusCollection);
$id = 0;
foreach ($statuses as $provider => $providerStatus) {
$providerState = $this->sortStatuses($providerStatus);
$id++;
$classes = [
ReportStatus::NOTICE => 'notice',
ReportStatus::INFO => 'info',
ReportStatus::OK => 'success',
ReportStatus::WARNING => 'warning',
ReportStatus::ERROR => 'danger'
];
$messages = '';
/** @var ReportStatus $status */
foreach ($providerState as $status) {
$severity = $status->getSeverity();
$messages .= strtr($template, [
'###CLASS###' => $classes[$severity],
'###HEADER###' => $status->getTitle(),
'###STATUS###' => $status->getValue(),
'###CONTENT###' => $status->getMessage()
]);
}
$header = '<h2>' . $provider . '</h2>';
$table = '<table class="table table-striped table-hover">';
$table .= '<tbody>' . $messages . '</tbody>';
$table .= '</table>';
$content .= $header . $table;
}
return $content;
}
/**
* Sorts the status providers (alphabetically and puts primary status providers at the beginning)
*
* @param array $statusCollection A collection of statuses (with providers)
* @return array The collection of statuses sorted by provider (beginning with provider "_install")
*/
protected function sortStatusProviders(array $statusCollection)
{
// Extract the primary status collections, i.e. the status groups
// that must appear on top of the status report
// Change their keys to localized collection titles
$primaryStatuses = [
$this->getLanguageService()->getLL('status_typo3') => $statusCollection['typo3'],
$this->getLanguageService()->getLL('status_system') => $statusCollection['system'],
$this->getLanguageService()->getLL('status_security') => $statusCollection['security'],
$this->getLanguageService()->getLL('status_configuration') => $statusCollection['configuration']
];
unset($statusCollection['typo3'], $statusCollection['system'], $statusCollection['security'], $statusCollection['configuration']);
// Assemble list of secondary status collections with left-over collections
// Change their keys using localized labels if available
// @todo extract into getLabel() method
$secondaryStatuses = [];
foreach ($statusCollection as $statusProviderId => $collection) {
$label = '';
if (strpos($statusProviderId, 'LLL:') === 0) {
// Label provided by extension
$label = $this->getLanguageService()->sL($statusProviderId);
} else {
// Generic label
$label = $this->getLanguageService()->getLL('status_' . $statusProviderId);
}
$providerLabel = empty($label) ? $statusProviderId : $label;
$secondaryStatuses[$providerLabel] = $collection;
}
// Sort the secondary status collections alphabetically
ksort($secondaryStatuses);
$orderedStatusCollection = array_merge($primaryStatuses, $secondaryStatuses);
return $orderedStatusCollection;
}
/**
* Sorts the statuses by severity
*
* @param array $statusCollection A collection of statuses per provider
* @return array The collection of statuses sorted by severity
*/
protected function sortStatuses(array $statusCollection)
{
$statuses = [];
$sortTitle = [];
$header = null;
/** @var ReportStatus $status */
foreach ($statusCollection as $status) {
if ($status->getTitle() === 'TYPO3') {
$header = $status;
continue;
}
$statuses[] = $status;
$sortTitle[] = $status->getSeverity();
}
array_multisort($sortTitle, SORT_DESC, $statuses);
// Making sure that the core version information is always on the top
if (is_object($header)) {
array_unshift($statuses, $header);
}
return $statuses;
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,90 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Performs several checks about the system's health
*/
class SystemStatus implements StatusProviderInterface
{
/**
* Determines the Install Tool's status, mainly concerning its protection.
*
* @return array List of statuses
*/
public function getStatus()
{
$statuses = [
'PhpModules' => $this->getMissingPhpModulesOfExtensions()
];
return $statuses;
}
/**
* Reports whether extensions need additional PHP modules different from standard core requirements
*
* @return \TYPO3\CMS\Reports\Status A status of missing PHP modules
*/
protected function getMissingPhpModulesOfExtensions()
{
$modules = [];
if (is_array(${$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install/mod/class.tx_install.php']['requiredPhpModules']})) {
foreach (${$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install/mod/class.tx_install.php']['requiredPhpModules']} as $className) {
$hookObject = GeneralUtility::makeInstance($className);
$modules = $hookObject->setRequiredPhpModules($modules, $this);
}
}
$missingPhpModules = [];
foreach ($modules as $module) {
if (is_array($module)) {
$detectedSubmodules = false;
foreach ($module as $submodule) {
if (extension_loaded($submodule)) {
$detectedSubmodules = true;
}
}
if ($detectedSubmodules === false) {
$missingPhpModules[] = sprintf($this->getLanguageService()->getLL('status_phpModulesGroup'), '(' . implode(', ', $module) . ')');
}
} elseif (!extension_loaded($module)) {
$missingPhpModules[] = $module;
}
}
if (!empty($missingPhpModules)) {
$value = $this->getLanguageService()->getLL('status_phpModulesMissing');
$message = sprintf($this->getLanguageService()->getLL('status_phpModulesList'), implode(', ', $missingPhpModules));
$message .= ' ' . $this->getLanguageService()->getLL('status_phpModulesInfo');
$severity = ReportStatus::ERROR;
} else {
$value = $this->getLanguageService()->getLL('status_phpModulesPresent');
$message = '';
$severity = ReportStatus::OK;
}
return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_phpModules'), $value, $message, $severity);
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,90 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Performs basic checks about the TYPO3 install
*/
class Typo3Status implements StatusProviderInterface
{
/**
* Returns the status for this report
*
* @return array List of statuses
*/
public function getStatus()
{
$statuses = [
'registeredXclass' => $this->getRegisteredXclassStatus(),
];
return $statuses;
}
/**
* List any Xclasses registered in the system
*
* @return \TYPO3\CMS\Reports\Status
*/
protected function getRegisteredXclassStatus()
{
$message = '';
$value = $this->getLanguageService()->getLL('status_none');
$severity = ReportStatus::OK;
$xclassFoundArray = [];
if (array_key_exists('Objects', $GLOBALS['TYPO3_CONF_VARS']['SYS'])) {
foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'] as $originalClass => $override) {
if (array_key_exists('className', $override)) {
$xclassFoundArray[$originalClass] = $override['className'];
}
}
}
if (!empty($xclassFoundArray)) {
$value = $this->getLanguageService()->getLL('status_xclassUsageFound');
$message = $this->getLanguageService()->getLL('status_xclassUsageFound_message') . '<br />';
$message .= '<ol>';
foreach ($xclassFoundArray as $originalClass => $xClassName) {
$messageDetail = sprintf(
$this->getLanguageService()->getLL('status_xclassUsageFound_message_detail'),
'<code>' . htmlspecialchars($originalClass) . '</code>',
'<code>' . htmlspecialchars($xClassName) . '</code>'
);
$message .= '<li>' . $messageDetail . '</li>';
}
$message .= '</ol>';
$severity = ReportStatus::NOTICE;
}
return GeneralUtility::makeInstance(
ReportStatus::class,
$this->getLanguageService()->getLL('status_xclassUsage'),
$value,
$message,
$severity
);
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,80 @@
<?php
namespace TYPO3\CMS\Reports\Report\Status;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status as ReportStatus;
/**
* Post processes the warning messages found in about modules.
*/
class WarningMessagePostProcessor
{
/**
* Tries to get the highest severity of the system's status first, if
* something is found it is assumed that the status update task is set up
* properly or the status report has been checked manually and we take over
* control over the system warning messages.
*
* @param array $warningMessages An array of messages related to already found issues.
*/
public function displayWarningMessages_postProcess(array &$warningMessages)
{
if (!$this->getBackendUser()->isAdmin()) {
return;
}
// Get highest severity
/** @var Registry $registry */
$registry = GeneralUtility::makeInstance(Registry::class);
$highestSeverity = $registry->get('tx_reports', 'status.highestSeverity', null);
if ($highestSeverity !== null) {
if ($highestSeverity > ReportStatus::OK) {
// Display a message that there's something wrong and that
// the admin should take a look at the detailed status report
$this->getLanguageService()->includeLLFile('EXT:reports/Resources/Private/Language/locallang_reports.xlf');
$reportModuleIdentifier = 'system_ReportsTxreportsm1';
$reportModuleParameters = [
'tx_reports_system_reportstxreportsm1[extension]=tx_reports',
'tx_reports_system_reportstxreportsm1[report]=status',
'tx_reports_system_reportstxreportsm1[action]=detail',
'tx_reports_system_reportstxreportsm1[controller]=Report',
];
$warningMessages['tx_reports_status_notification'] = sprintf(
$this->getLanguageService()->getLL('status_problemNotification'),
'<a href="javascript:top.goToModule(' . GeneralUtility::quoteJSvalue($reportModuleIdentifier) . ', 1, ' . GeneralUtility::quoteJSvalue('&' . implode('&', $reportModuleParameters)) . ');">',
'</a>'
);
}
}
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser()
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,28 @@
<?php
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Interface for classes which provide a report.
*/
interface ReportInterface
{
/**
* Returns the content for a report
*
* @return string A reports rendered HTML
*/
public function getReport();
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ServerRequestInterface;
/**
* Interface for classes which provide a report using information from the current request
*/
interface RequestAwareReportInterface extends ReportInterface
{
/**
* Returns the content for a report
*
* @param ServerRequestInterface|null $request the currently handled request
* @return string A reports rendered HTML
*/
public function getReport(ServerRequestInterface $request = null);
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ServerRequestInterface;
/**
* Interface for classes which provide a status report entry using information from the current request
*/
interface RequestAwareStatusProviderInterface extends StatusProviderInterface
{
/**
* Returns the status of an extension or (sub)system
*
* @param ServerRequestInterface|null $request the currently handled request
* @return array An array of \TYPO3\CMS\Reports\Status objects
*/
public function getStatus(ServerRequestInterface $request = null);
}
+127
View File
@@ -0,0 +1,127 @@
<?php
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Utility\MathUtility;
/**
* A class representing a certain status
*/
class Status
{
const NOTICE = -2;
const INFO = -1;
const OK = 0;
const WARNING = 1;
const ERROR = 2;
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $value;
/**
* @var string
*/
protected $message;
/**
* @var int
*/
protected $severity;
/**
* Construct a status
*
* All values must be given as constructor arguments.
* All strings should be localized.
*
* @param string $title Status title, eg. "Deprecation log"
* @param string $value Status value, eg. "Disabled"
* @param string $message Optional message further describing the title/value combination
* Example:, eg "The deprecation log is important and does foo, to disable it do bar"
* @param int $severity A severity level. Use one of the constants above!
*/
public function __construct($title, $value, $message = '', $severity = self::OK)
{
$this->title = (string)$title;
$this->value = (string)$value;
$this->message = (string)$message;
$this->severity = MathUtility::forceIntegerInRange($severity, self::NOTICE, self::ERROR, self::OK);
}
/**
* Gets the status' title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Gets the status' value
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Gets the status' message (if any)
*
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* Gets the status' severity
*
* @return int
*/
public function getSeverity()
{
return $this->severity;
}
/**
* Creates a string representation of a status.
*
* @return string String representation of this status.
*/
public function __toString()
{
$severity = [
self::NOTICE => 'NOTE',
self::INFO => 'INFO',
self::OK => 'OK',
self::WARNING => 'WARN',
self::ERROR => 'ERR'
];
// Max length 80 characters
$stringRepresentation = str_pad('[' . $severity[$this->severity] . ']', 7) . str_pad($this->title, 40) . ' - ' . substr($this->value, 0, 30);
return $stringRepresentation;
}
}
@@ -0,0 +1,28 @@
<?php
namespace TYPO3\CMS\Reports;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Interface for classes which provide a status report entry.
*/
interface StatusProviderInterface
{
/**
* Returns the status of an extension or (sub)system
*
* @return array An array of \TYPO3\CMS\Reports\Status objects
*/
public function getStatus();
}
@@ -0,0 +1,147 @@
<?php
namespace TYPO3\CMS\Reports\Task;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Mail\MailMessage;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\Status;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
/**
* A task that should be run regularly to determine the system's status.
* @internal This class is a specific scheduler task implementation and is not considered part of the Public TYPO3 API.
*/
class SystemStatusUpdateTask extends AbstractTask
{
/**
* Email addresses to send email notification to in case we find problems with
* the system.
*
* @var string
*/
protected $notificationEmail;
/**
* Checkbox for to send all types of notification, not only problems
*
* @var bool
*/
protected $notificationAll = false;
/**
* Executes the System Status Update task, determining the highest severity of
* status reports and saving that to the registry to be displayed at login
* if necessary.
*
* @see \TYPO3\CMS\Scheduler\Task\AbstractTask::execute()
*/
public function execute()
{
/** @var Registry $registry */
$registry = GeneralUtility::makeInstance(Registry::class);
/** @var \TYPO3\CMS\Reports\Report\Status\Status $statusReport */
$statusReport = GeneralUtility::makeInstance(\TYPO3\CMS\Reports\Report\Status\Status::class);
$systemStatus = $statusReport->getDetailedSystemStatus();
$highestSeverity = $statusReport->getHighestSeverity($systemStatus);
$registry->set('tx_reports', 'status.highestSeverity', $highestSeverity);
if (($highestSeverity > Status::OK) || $this->getNotificationAll()) {
$this->sendNotificationEmail($systemStatus);
}
return true;
}
/**
* Gets the notification email addresses.
*
* @return string Notification email addresses.
*/
public function getNotificationEmail()
{
return $this->notificationEmail;
}
/**
* Sets the notification email address.
*
* @param string $notificationEmail Notification email address.
*/
public function setNotificationEmail($notificationEmail)
{
$this->notificationEmail = $notificationEmail;
}
/**
* Sends a notification email, reporting system issues.
*
* @param Status[] $systemStatus Array of statuses
*/
protected function sendNotificationEmail(array $systemStatus)
{
$systemIssues = [];
foreach ($systemStatus as $statusProvider) {
/** @var Status $status */
foreach ($statusProvider as $status) {
if ($this->getNotificationAll() || ($status->getSeverity() > Status::OK)) {
$systemIssues[] = (string)$status . CRLF . $status->getMessage() . CRLF . CRLF;
}
}
}
$notificationEmails = GeneralUtility::trimExplode(LF, $this->notificationEmail, true);
$sendEmailsTo = [];
foreach ($notificationEmails as $notificationEmail) {
$sendEmailsTo[] = $notificationEmail;
}
$subject = sprintf($this->getLanguageService()->getLL('status_updateTask_email_subject'), $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
$message = $this->getNotificationAll() ? $this->getLanguageService()->getLL('status_allNotification') : $this->getLanguageService()->getLL('status_problemNotification');
$message .= CRLF . CRLF;
$message .= $this->getLanguageService()->getLL('status_updateTask_email_site') . ': ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
$message .= CRLF . CRLF;
$message .= $this->getLanguageService()->getLL('status_updateTask_email_issues') . ': ' . CRLF;
$message .= implode(CRLF, $systemIssues);
$message .= CRLF . CRLF;
/** @var MailMessage $mail */
$mail = GeneralUtility::makeInstance(MailMessage::class);
$mail->setTo($sendEmailsTo);
$mail->setSubject($subject);
$mail->setBody($message);
$mail->send();
}
/**
* @return bool
*/
public function getNotificationAll(): bool
{
return $this->notificationAll;
}
/**
* @param bool $notificationAll
*/
public function setNotificationAll(bool $notificationAll)
{
$this->notificationAll = $notificationAll;
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,148 @@
<?php
namespace TYPO3\CMS\Reports\Task;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
/**
* Additional field to set the notification email address(es) for system health
* issue notifications.
* @internal This class is a specific scheduler task implementation and is not considered part of the Public TYPO3 API.
*/
class SystemStatusUpdateTaskNotificationEmailField extends AbstractAdditionalFieldProvider
{
/**
* Additional fields
*
* @var array
*/
protected $fields = ['notificationEmail', 'notificationAll'];
/**
* Field prefix.
*
* @var string
*/
protected $fieldPrefix = 'SystemStatusUpdate';
/**
* Gets additional fields to render in the form to add/edit a task
*
* @param array $taskInfo Values of the fields from the add/edit task form
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
* @param SchedulerModuleController $schedulerModule Reference to the scheduler backend module
* @return array A two dimensional array, array('Identifier' => array('fieldId' => array('code' => '', 'label' => '', 'cshKey' => '', 'cshLabel' => ''))
*/
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
{
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
if ($currentSchedulerModuleAction->equals(Action::EDIT)) {
$taskInfo[$this->fieldPrefix . 'NotificationEmail'] = $task->getNotificationEmail();
$taskInfo[$this->fieldPrefix . 'NotificationAll'] = $task->getNotificationAll();
}
// build html for additional email field
$fieldName = $this->getFullFieldName('notificationEmail');
$fieldId = 'task_' . $fieldName;
$fieldHtml = '<textarea class="form-control" ' . 'rows="5" cols="50" name="tx_scheduler[' . $fieldName . ']" ' . 'id="' . $fieldId . '" ' . '>' . htmlspecialchars($taskInfo[$fieldName]) . '</textarea>';
$additionalFields = [];
$additionalFields[$fieldId] = [
'code' => $fieldHtml,
'label' => 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_updateTaskField_notificationEmails',
'cshKey' => '',
'cshLabel' => $fieldId
];
// build html for additional mail all checkbox field
$fieldName = $this->getFullFieldName('notificationAll');
$fieldId = 'task_' . $fieldName;
$fieldHtml = '<input type="checkbox" name="tx_scheduler[' . $fieldName . ']" id="' . $fieldId . '" value="1"' . ($taskInfo[$fieldName] ? ' checked="checked"' : '') . '>';
$additionalFields[$fieldId] = [
'code' => $fieldHtml,
'label' => 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_updateTaskField_notificationAll',
'cshKey' => '',
'cshLabel' => $fieldId
];
return $additionalFields;
}
/**
* Validates the additional fields' values
*
* @param array $submittedData An array containing the data submitted by the add/edit task form
* @param SchedulerModuleController $schedulerModule Reference to the scheduler backend module
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
*/
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
{
$validInput = true;
$notificationEmails = GeneralUtility::trimExplode(LF, $submittedData[$this->fieldPrefix . 'NotificationEmail'], true);
foreach ($notificationEmails as $notificationEmail) {
if (!GeneralUtility::validEmail($notificationEmail)) {
$validInput = false;
break;
}
}
if (!$validInput || empty($submittedData[$this->fieldPrefix . 'NotificationEmail'])) {
$this->addMessage($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_updateTaskField_notificationEmails_invalid'), FlashMessage::ERROR);
$validInput = false;
}
return $validInput;
}
/**
* Takes care of saving the additional fields' values in the task's object
*
* @param array $submittedData An array containing the data submitted by the add/edit task form
* @param AbstractTask $task Reference to the scheduler backend module
* @throws \InvalidArgumentException
*/
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
{
if (!$task instanceof SystemStatusUpdateTask) {
throw new \InvalidArgumentException('Expected a task of type ' . SystemStatusUpdateTask::class . ', but got ' . get_class($task), 1295012802);
}
$task->setNotificationEmail($submittedData[$this->fieldPrefix . 'NotificationEmail']);
$task->setNotificationAll(!empty($submittedData[$this->fieldPrefix . 'NotificationAll']));
}
/**
* Constructs the full field name which can be used in HTML markup.
*
* @param string $fieldName A raw field name
* @return string Field name ready to use in HTML markup
*/
protected function getFullFieldName($fieldName)
{
return $this->fieldPrefix . ucfirst($fieldName);
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,85 @@
<?php
namespace TYPO3\CMS\Reports\ViewHelpers;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
/**
* Render the icon of a report
*
* @internal
*/
class IconViewHelper extends AbstractBackendViewHelper
{
/**
* As this ViewHelper renders HTML, the output must not be escaped.
*
* @var bool
*/
protected $escapeOutput = false;
/**
* Initializes the arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('icon', 'string', 'Icon to be used', true);
$this->registerArgument('title', 'string', 'Optional title', false, '');
}
/**
* Renders the icon
*
* @return string Content rendered image
*/
public function render()
{
return static::renderStatic(
$this->arguments,
$this->buildRenderChildrenClosure(),
$this->renderingContext
);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return string
*/
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
$icon = $arguments['icon'];
$title = $arguments['title'];
$icon = GeneralUtility::getFileAbsFileName($icon);
// use the extension icon from EXT:reports as fallback
if (!$icon) {
$fallbackIconPath = ExtensionManagementUtility::extPath('reports');
$icon = ExtensionManagementUtility::getExtensionIcon($fallbackIconPath, true);
}
return '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($icon))
. '" width="16" height="16" title="' . htmlspecialchars($title)
. '" alt="' . htmlspecialchars($title) . '" />';
}
}