TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,879 @@
|
||||
<?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\Install\SystemEnvironment;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Information\Typo3Information;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Check system environment status.
|
||||
*
|
||||
* This class is a hardcoded requirement check of the underlying
|
||||
* server and PHP system.
|
||||
*
|
||||
* The class *must not* check for any TYPO3 specific things like
|
||||
* specific configuration values or directories.
|
||||
*
|
||||
* This class is instantiated as a very early during installation.
|
||||
*
|
||||
* Be picky with dependencies here:
|
||||
* * No hooks or anything like that
|
||||
* * No localization
|
||||
* * Only low level ext:core classes if free of side effects
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Check implements CheckInterface
|
||||
{
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
/**
|
||||
* @var array List of required PHP extensions
|
||||
*/
|
||||
protected $requiredPhpExtensions = [
|
||||
'filter',
|
||||
'gd',
|
||||
'intl',
|
||||
'json',
|
||||
'libxml',
|
||||
'mbstring',
|
||||
'PDO',
|
||||
'session',
|
||||
'SPL',
|
||||
'standard',
|
||||
'tokenizer',
|
||||
'xml',
|
||||
'zip',
|
||||
'zlib',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $suggestedPhpExtensions = [
|
||||
'exif' => 'This extension is used to detect the orientation of uploaded images.',
|
||||
'fileinfo' => 'This extension is used for proper file type detection in the File Abstraction Layer.',
|
||||
'openssl' => 'This extension is used for sending SMTP mails over an encrypted channel endpoint.',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->messageQueue = new FlashMessageQueue('install');
|
||||
}
|
||||
|
||||
public function getMessageQueue(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$this->checkCurrentDirectoryIsInIncludePath();
|
||||
$this->checkFileUploadEnabled();
|
||||
$this->checkPostUploadSizeIsHigherOrEqualMaximumFileUploadSize();
|
||||
$this->checkMemorySettings();
|
||||
$this->checkPhpVersion();
|
||||
$this->checkMaxExecutionTime();
|
||||
$this->checkDisableFunctions();
|
||||
$this->checkDocRoot();
|
||||
$this->checkOpenBaseDir();
|
||||
$this->checkXdebugMaxNestingLevel();
|
||||
|
||||
$this->checkMaxInputVars();
|
||||
$this->checkReflectionDocComment();
|
||||
$this->checkWindowsApacheThreadStackSize();
|
||||
|
||||
foreach ($this->requiredPhpExtensions as $extension) {
|
||||
$this->checkPhpExtension($extension);
|
||||
}
|
||||
|
||||
foreach ($this->suggestedPhpExtensions as $extension => $purpose) {
|
||||
$this->checkPhpExtension($extension, false, $purpose);
|
||||
}
|
||||
|
||||
$this->checkPcreVersion();
|
||||
$this->checkGdLibTrueColorSupport();
|
||||
$this->checkGdLibGifSupport();
|
||||
$this->checkGdLibJpgSupport();
|
||||
$this->checkGdLibPngSupport();
|
||||
$this->checkGdLibFreeTypeSupport();
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current directory (.) is in PHP include path
|
||||
*/
|
||||
protected function checkCurrentDirectoryIsInIncludePath()
|
||||
{
|
||||
$includePath = (string)ini_get('include_path');
|
||||
$delimiter = $this->isWindowsOs() ? ';' : ':';
|
||||
$pathArray = GeneralUtility::trimExplode($delimiter, $includePath, true);
|
||||
if (!in_array('.', $pathArray)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'include_path = ' . implode(' ', $pathArray) . LF
|
||||
. 'Normally the current path \'.\' is included in the'
|
||||
. ' include_path of PHP. Although TYPO3 does not rely on this,'
|
||||
. ' it is an unusual setting that may introduce problems for'
|
||||
. ' some extensions.',
|
||||
'Current directory (./) is not within PHP include path',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Current directory (./) is within PHP include path.'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file uploads are enabled in PHP
|
||||
*/
|
||||
protected function checkFileUploadEnabled()
|
||||
{
|
||||
if (!ini_get('file_uploads')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'file_uploads=' . ini_get('file_uploads') . LF
|
||||
. 'TYPO3 uses the ability to upload files from the browser in various cases.'
|
||||
. ' If this flag is disabled in PHP, you won\'t be able to upload files.'
|
||||
. ' But it doesn\'t end here, because not only are files not accepted by'
|
||||
. ' the server - ALL content in the forms are discarded and therefore'
|
||||
. ' nothing at all will be editable if you don\'t set this flag!',
|
||||
'File uploads not allowed in PHP',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'File uploads allowed in PHP'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check maximum post upload size correlates with maximum file upload
|
||||
*/
|
||||
protected function checkPostUploadSizeIsHigherOrEqualMaximumFileUploadSize()
|
||||
{
|
||||
$maximumUploadFilesize = $this->getBytesFromSizeMeasurement((string)ini_get('upload_max_filesize'));
|
||||
$maximumPostSize = $this->getBytesFromSizeMeasurement((string)ini_get('post_max_size'));
|
||||
if ($maximumPostSize > 0 && $maximumPostSize < $maximumUploadFilesize) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'upload_max_filesize=' . ini_get('upload_max_filesize') . LF
|
||||
. 'post_max_size=' . ini_get('post_max_size') . LF
|
||||
. 'You have defined a maximum size for file uploads in PHP which'
|
||||
. ' exceeds the allowed size for POST requests. Therefore the'
|
||||
. ' file uploads can also not be larger than ' . ini_get('post_max_size') . '.',
|
||||
'Maximum size for POST requests is smaller than maximum upload filesize in PHP',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif ($maximumPostSize === $maximumUploadFilesize) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'The maximum size for file uploads is set to ' . ini_get('upload_max_filesize'),
|
||||
'Maximum post upload size correlates with maximum upload file size in PHP'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'The maximum size for file uploads is set to ' . ini_get('upload_max_filesize'),
|
||||
'Maximum post upload size is higher than maximum upload file size in PHP, which is fine.'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check memory settings
|
||||
*/
|
||||
protected function checkMemorySettings()
|
||||
{
|
||||
$minimumMemoryLimit = 256;
|
||||
$recommendedMemoryLimit = 512;
|
||||
$memoryLimit = $this->getBytesFromSizeMeasurement((string)ini_get('memory_limit'));
|
||||
if ($memoryLimit <= 0) {
|
||||
if (Environment::isCli()) {
|
||||
// "0" memory limit for CLI is usually "just fine". Do not cause a report for this in CLI mode (but in web mode)
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Maximum PHP memory limit is set to zero; this is commonly set in PHP CLI mode, which is currently active for this check.',
|
||||
'Unlimited memory limit for PHP (CLI)',
|
||||
));
|
||||
return;
|
||||
}
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'PHP is configured not to limit memory usage at all. This is a risk'
|
||||
. ' and should be avoided in production setup. In general it\'s best practice to limit this.'
|
||||
. ' To be safe, set a limit in PHP, but with a minimum of ' . $recommendedMemoryLimit . 'MB:' . LF
|
||||
. 'memory_limit=' . $recommendedMemoryLimit . 'M',
|
||||
'Unlimited memory limit for PHP',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} elseif ($memoryLimit < 1024 * 1024 * $minimumMemoryLimit) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'memory_limit=' . ini_get('memory_limit') . LF
|
||||
. 'Your system is configured to enforce a memory limit for PHP scripts lower than '
|
||||
. $minimumMemoryLimit . 'MB. It is required to raise the limit.'
|
||||
. ' We recommend a minimum PHP memory limit of ' . $recommendedMemoryLimit . 'MB:' . LF
|
||||
. 'memory_limit=' . $recommendedMemoryLimit . 'M',
|
||||
'PHP Memory limit below ' . $minimumMemoryLimit . 'MB',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif ($memoryLimit < 1024 * 1024 * $recommendedMemoryLimit) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'memory_limit=' . ini_get('memory_limit') . LF
|
||||
. 'Your system is configured to enforce a memory limit for PHP scripts lower than '
|
||||
. $recommendedMemoryLimit . 'MB.'
|
||||
. ' A slim TYPO3 instance without many extensions will probably work, but you should monitor your'
|
||||
. ' system for "allowed memory size of X bytes exhausted" messages, especially if using the backend.'
|
||||
. ' To be on the safe side, we recommend a minimum PHP memory limit of '
|
||||
. $recommendedMemoryLimit . 'MB:' . LF
|
||||
. 'memory_limit=' . $recommendedMemoryLimit . 'M',
|
||||
'PHP Memory limit below ' . $recommendedMemoryLimit . 'MB',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP Memory limit is equal to or more than ' . $recommendedMemoryLimit . 'MB'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check minimum PHP version
|
||||
*/
|
||||
protected function checkPhpVersion()
|
||||
{
|
||||
$minimumPhpVersion = '8.5.0';
|
||||
$currentPhpVersion = PHP_VERSION;
|
||||
if (version_compare($currentPhpVersion, $minimumPhpVersion) < 0) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your PHP version ' . $currentPhpVersion . ' is too old. TYPO3 CMS does not run'
|
||||
. ' with this version. Update to at least PHP ' . $minimumPhpVersion,
|
||||
'PHP version too low',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP version is fine'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PRCE module is loaded and minimum version
|
||||
*/
|
||||
protected function checkPcreVersion()
|
||||
{
|
||||
$minimumPcreVersion = '8.38';
|
||||
if (!extension_loaded('pcre')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'TYPO3 CMS uses PHP extension pcre but it is not loaded'
|
||||
. ' in your environment. Change your environment to provide this extension'
|
||||
. ' in with minimum version ' . $minimumPcreVersion . '.',
|
||||
'PHP extension pcre not loaded',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$installedPcreVersionString = trim(PCRE_VERSION); // '8.39 2016-06-14'
|
||||
$mainPcreVersionString = explode(' ', $installedPcreVersionString);
|
||||
$mainPcreVersionString = $mainPcreVersionString[0]; // '8.39'
|
||||
if (version_compare($mainPcreVersionString, $minimumPcreVersion) < 0) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your PCRE version ' . PCRE_VERSION . ' is too old. TYPO3 CMS may trigger PHP segmentantion'
|
||||
. ' faults with this version. Update to at least PCRE ' . $minimumPcreVersion,
|
||||
'PCRE version too low',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP extension PCRE is loaded and version is fine'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check maximum execution time
|
||||
*/
|
||||
protected function checkMaxExecutionTime()
|
||||
{
|
||||
$minimumMaximumExecutionTime = 30;
|
||||
$recommendedMaximumExecutionTime = 240;
|
||||
$currentMaximumExecutionTime = ini_get('max_execution_time');
|
||||
if ($currentMaximumExecutionTime == 0) {
|
||||
if (Environment::isCli()) {
|
||||
// "0" execution time for CLI is usually "just fine". Do not cause a report for this in CLI mode (but in web mode)
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Maximum PHP script execution time is set to zero; this is commonly set in PHP CLI mode, which is currently active for this check.',
|
||||
'Infinite PHP script execution time',
|
||||
));
|
||||
return;
|
||||
}
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'max_execution_time=0' . LF
|
||||
. 'While TYPO3 is fine with this, you risk a denial-of-service for your system if for whatever'
|
||||
. ' reason some script hangs in an infinite loop. You are usually on the safe side '
|
||||
. ' if it is reduced to ' . $recommendedMaximumExecutionTime . ' seconds:' . LF
|
||||
. 'max_execution_time=' . $recommendedMaximumExecutionTime,
|
||||
'Infinite PHP script execution time',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} elseif ($currentMaximumExecutionTime < $minimumMaximumExecutionTime) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'max_execution_time=' . $currentMaximumExecutionTime . LF
|
||||
. 'Your max_execution_time is too low. Some expensive operations in TYPO3 can take longer than that.'
|
||||
. ' It is recommended to raise the limit to ' . $recommendedMaximumExecutionTime . ' seconds:' . LF
|
||||
. 'max_execution_time=' . $recommendedMaximumExecutionTime,
|
||||
'Low PHP script execution time',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif ($currentMaximumExecutionTime < $recommendedMaximumExecutionTime) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'max_execution_time=' . $currentMaximumExecutionTime . LF
|
||||
. 'Your max_execution_time is low. While TYPO3 often runs without problems'
|
||||
. ' with ' . $minimumMaximumExecutionTime . ' seconds,'
|
||||
. ' it may still happen that script execution is stopped before finishing'
|
||||
. ' calculations. You should monitor the system for messages in this area'
|
||||
. ' and maybe raise the limit to ' . $recommendedMaximumExecutionTime . ' seconds:' . LF
|
||||
. 'max_execution_time=' . $recommendedMaximumExecutionTime,
|
||||
'Low PHP script execution time',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Maximum PHP script execution time is equal to or more than ' . $recommendedMaximumExecutionTime
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for disabled functions
|
||||
*/
|
||||
protected function checkDisableFunctions()
|
||||
{
|
||||
$disabledFunctions = trim((string)ini_get('disable_functions'));
|
||||
|
||||
// Filter "disable_functions"
|
||||
$disabledFunctionsArray = GeneralUtility::trimExplode(',', $disabledFunctions, true);
|
||||
|
||||
// Array with strings to find
|
||||
$findStrings = [
|
||||
// Disabled by default on Ubuntu OS but this is okay since the Core does not use them
|
||||
'pcntl_',
|
||||
];
|
||||
|
||||
// List of disable_functions which would not trigger an error, but only a warning
|
||||
$configuredAllowedDisableFunctions = $GLOBALS['TYPO3_CONF_VARS']['SYS']['allowedPhpDisableFunctions'] ?? [];
|
||||
if (!is_array($configuredAllowedDisableFunctions)) {
|
||||
$configuredAllowedDisableFunctions = [];
|
||||
}
|
||||
$foundAllowedDisableFunctions = [];
|
||||
|
||||
// Iterate all functions that are currently disabled by PHP ($disabledFunctionsArray).
|
||||
// Each disabled function ($disabledFunction) is checked whether it may be acceptable
|
||||
// to be disabled (based on $configuredAllowedDisableFunctions).
|
||||
// If good to be disabled: Remove from $disabledFunctionsArray (which is emitted later on),
|
||||
// and also add to $foundAllowedDisableFunctions for reporting.
|
||||
// Else: Check if the function is maybe whitelisted because unused by core ($findStrings)
|
||||
// and if so, also remove from $disabledFunctionsArray
|
||||
// What remains then in $disabledFunctionsArray is the list of disabled functions that
|
||||
// need to be reported.
|
||||
foreach ($disabledFunctionsArray as $key => $disabledFunction) {
|
||||
if (in_array($disabledFunction, $configuredAllowedDisableFunctions, true)) {
|
||||
unset($disabledFunctionsArray[$key]);
|
||||
$foundAllowedDisableFunctions[] = $disabledFunction;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($findStrings as $findString) {
|
||||
if (str_contains($disabledFunction, $findString)) {
|
||||
unset($disabledFunctionsArray[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($disabledFunctions !== '') {
|
||||
// Error for disable_functions which are not explicitly allowed
|
||||
if ($disabledFunctionsArray !== []) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'disable_functions=' . implode(' ', $disabledFunctionsArray) . LF
|
||||
. '- These function(s) are disabled. TYPO3 uses some of those, so there might be trouble.'
|
||||
. ' TYPO3 is designed to use the default set of PHP functions plus some common extensions.'
|
||||
. ' Possibly these functions are disabled'
|
||||
. ' due to security considerations and often the list would include a function like'
|
||||
. ' exec() which is used by TYPO3 at various places. Depending on which exact functions'
|
||||
. ' are disabled, some parts of the system may just break without further notice. Known acceptable'
|
||||
. ' exemptions can be muted by adding those to'
|
||||
. ' $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'allowedPhpDisableFunctions\'] in your system'
|
||||
. ' configuration.',
|
||||
'Some PHP functions disabled',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
// Warning for disable_functions which are explicitly allowed
|
||||
if ($foundAllowedDisableFunctions !== []) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'disable_functions=' . implode(' ', $foundAllowedDisableFunctions) . LF
|
||||
. '- These function(s) are disabled. TYPO3 or installed extensions may use some of those, but the'
|
||||
. ' error reporting for them is explicitly muted in your installation via configuration variable'
|
||||
. ' $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'allowedPhpDisableFunctions\'].'
|
||||
. ' Please ensure by yourself that these functions are not used by TYPO3 or any other installed package.',
|
||||
'Some PHP functions disabled, but considered irrelevant',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
}
|
||||
// Notice for known irrelevant functions (e.g. pcntl_*)
|
||||
// Only shown if none of the two FlashMessages above are emitted.
|
||||
if ($disabledFunctionsArray === [] && $foundAllowedDisableFunctions === []) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'disable_functions=' . implode(' ', explode(',', $disabledFunctions)) . LF
|
||||
. '- These function(s) are disabled. TYPO3 uses currently none of those, so you are good to go.',
|
||||
'Some PHP functions currently disabled but OK'
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'No disabled PHP functions'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for doc_root ini setting
|
||||
*/
|
||||
protected function checkDocRoot()
|
||||
{
|
||||
$docRootSetting = trim((string)ini_get('doc_root'));
|
||||
if ($docRootSetting !== '') {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'doc_root=' . $docRootSetting . LF
|
||||
. 'PHP cannot execute scripts'
|
||||
. ' outside this directory. This setting is seldom used and must correlate'
|
||||
. ' with your actual document root. You might be in trouble if your'
|
||||
. ' TYPO3 CMS core code is linked to some different location.'
|
||||
. ' If that is a problem, the setting must be changed.',
|
||||
'doc_root is set',
|
||||
ContextualFeedbackSeverity::NOTICE
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP doc_root is not set'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check open_basedir
|
||||
*/
|
||||
protected function checkOpenBaseDir()
|
||||
{
|
||||
$openBaseDirSetting = trim((string)ini_get('open_basedir'));
|
||||
if ($openBaseDirSetting !== '') {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'open_basedir = ' . ini_get('open_basedir') . LF
|
||||
. 'This restricts TYPO3 to open and include files only in this'
|
||||
. ' path. Please make sure that this does not prevent TYPO3 from running,'
|
||||
. ' if for example your TYPO3 CMS core is linked to a different directory'
|
||||
. ' not included in this path.',
|
||||
'PHP open_basedir is set',
|
||||
ContextualFeedbackSeverity::NOTICE
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP open_basedir is off'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If xdebug is loaded, the default max_nesting_level of 100 must be raised
|
||||
*/
|
||||
protected function checkXdebugMaxNestingLevel()
|
||||
{
|
||||
if (extension_loaded('xdebug')) {
|
||||
$recommendedMaxNestingLevel = 400;
|
||||
$errorThreshold = 250;
|
||||
$currentMaxNestingLevel = ini_get('xdebug.max_nesting_level');
|
||||
if ($currentMaxNestingLevel < $errorThreshold) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'xdebug.max_nesting_level=' . $currentMaxNestingLevel . LF
|
||||
. 'This setting controls the maximum number of nested function calls to protect against'
|
||||
. ' infinite recursion. The current value is too low for TYPO3 CMS and must'
|
||||
. ' be either raised or xdebug has to be unloaded. A value of ' . $recommendedMaxNestingLevel
|
||||
. ' is recommended. Warning: Expect fatal PHP errors in central parts of the CMS'
|
||||
. ' if the value is not raised significantly to:' . LF
|
||||
. 'xdebug.max_nesting_level=' . $recommendedMaxNestingLevel,
|
||||
'PHP xdebug.max_nesting_level is critically low',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif ($currentMaxNestingLevel < $recommendedMaxNestingLevel) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'xdebug.max_nesting_level=' . $currentMaxNestingLevel . LF
|
||||
. 'This setting controls the maximum number of nested function calls to protect against'
|
||||
. ' infinite recursion. The current value is high enough for the TYPO3 CMS core to work'
|
||||
. ' fine, but still some extensions could raise fatal PHP errors if the setting is not'
|
||||
. ' raised further. A value of ' . $recommendedMaxNestingLevel . ' is recommended.' . LF
|
||||
. 'xdebug.max_nesting_level=' . $recommendedMaxNestingLevel,
|
||||
'PHP xdebug.max_nesting_level is low',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP xdebug.max_nesting_level ok'
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP xdebug extension not loaded'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max_input_vars status
|
||||
*/
|
||||
protected function checkMaxInputVars()
|
||||
{
|
||||
$recommendedMaxInputVars = 1500;
|
||||
$minimumMaxInputVars = 1000;
|
||||
$currentMaxInputVars = ini_get('max_input_vars');
|
||||
|
||||
if ($currentMaxInputVars < $minimumMaxInputVars) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'max_input_vars=' . $currentMaxInputVars . LF
|
||||
. 'This setting can lead to lost information if submitting forms with lots of data in TYPO3 CMS'
|
||||
. ' (as the install tool does). It is highly recommended to raise this'
|
||||
. ' to at least ' . $recommendedMaxInputVars . ':' . LF
|
||||
. 'max_input_vars=' . $recommendedMaxInputVars,
|
||||
'PHP max_input_vars too low',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif ($currentMaxInputVars < $recommendedMaxInputVars) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'max_input_vars=' . $currentMaxInputVars . LF
|
||||
. 'This setting can lead to lost information if submitting forms with lots of data in TYPO3 CMS'
|
||||
. ' (as the install tool does). It is highly recommended to raise this'
|
||||
. ' to at least ' . $recommendedMaxInputVars . ':' . LF
|
||||
. 'max_input_vars=' . $recommendedMaxInputVars,
|
||||
'PHP max_input_vars very low',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP max_input_vars ok'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check doc comments can be fetched by reflection
|
||||
*/
|
||||
protected function checkReflectionDocComment()
|
||||
{
|
||||
$testReflection = new \ReflectionMethod(static::class, __FUNCTION__);
|
||||
if ($testReflection->getDocComment() === false) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'TYPO3 CMS core extensions like extbase and fluid heavily rely on method'
|
||||
. ' comment parsing to fetch annotations and add magic belonging to them.'
|
||||
. ' This does not work in the current environment and so we cannot install'
|
||||
. ' TYPO3 CMS.' . LF
|
||||
. ' Here are some possibilities: ' . LF
|
||||
. '* In Zend OPcache you can disable saving/loading comments. If you are using'
|
||||
. ' Zend OPcache (included since PHP 5.5) then check your php.ini settings for'
|
||||
. ' opcache.save_comments and opcache.load_comments and enable them.' . LF
|
||||
. '* In Zend Optimizer+ you can disable saving comments. If you are using'
|
||||
. ' Zend Optimizer+ then check your php.ini settings for'
|
||||
. ' zend_optimizerplus.save_comments and enable it.' . LF
|
||||
. '* The PHP extension eaccelerator is known to break this if'
|
||||
. ' it is compiled without --with-eaccelerator-doc-comment-inclusion flag.'
|
||||
. ' This compile flag must be specified, otherwise TYPO3 CMS will not work.' . LF
|
||||
. 'For more information take a look in our documentation '
|
||||
. Typo3Information::getDocsLink('t3coreapi:troubleshooting-php-troubleshooting-opcode') . '.',
|
||||
'PHP Doc comment reflection broken',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP Doc comment reflection works'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks thread stack size if on windows with apache
|
||||
*/
|
||||
protected function checkWindowsApacheThreadStackSize()
|
||||
{
|
||||
if (Environment::isCli()) {
|
||||
return;
|
||||
}
|
||||
if ($this->isWindowsOs()
|
||||
&& str_starts_with($_SERVER['SERVER_SOFTWARE'], 'Apache')
|
||||
) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'This current value cannot be checked by the system, so please ignore this warning if it'
|
||||
. ' is already taken care of: Fluid uses complex regular expressions which require a lot'
|
||||
. ' of stack space during the first processing.'
|
||||
. ' On Windows the default stack size for Apache is a lot smaller than on UNIX.'
|
||||
. ' You can increase the size to 8MB (default on UNIX) by adding the following configuration'
|
||||
. ' to httpd.conf and restarting Apache afterwards:' . LF
|
||||
. '<IfModule mpm_winnt_module>ThreadStackSize 8388608</IfModule>',
|
||||
'Windows apache thread stack size',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Apache ThreadStackSize is not an issue on UNIX systems'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific PHP extension is loaded.
|
||||
*/
|
||||
public function checkPhpExtension(string $extension, bool $required = true, string $purpose = '')
|
||||
{
|
||||
if (!extension_loaded($extension)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'TYPO3 uses the PHP extension "' . $extension . '" but it is not loaded'
|
||||
. ' in your environment. Change your environment to provide this extension. '
|
||||
. $purpose,
|
||||
'PHP extension "' . $extension . '" not loaded',
|
||||
$required ? ContextualFeedbackSeverity::ERROR : ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP extension "' . $extension . '" loaded'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check imagecreatetruecolor to verify gdlib works as expected
|
||||
*/
|
||||
protected function checkGdLibTrueColorSupport()
|
||||
{
|
||||
if (function_exists('imagecreatetruecolor')) {
|
||||
$imageResource = @imagecreatetruecolor(50, 100);
|
||||
if ($this->checkImageResource($imageResource)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP GD library true color works'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD is loaded, but calling imagecreatetruecolor() fails.'
|
||||
. ' This must be fixed, TYPO3 CMS won\'t work well otherwise.',
|
||||
'PHP GD library true color support broken',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Gdlib is essential for TYPO3 CMS to work properly.',
|
||||
'PHP GD library true color support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check gif support of GD library
|
||||
*/
|
||||
protected function checkGdLibGifSupport()
|
||||
{
|
||||
if (function_exists('imagecreatefromgif')
|
||||
&& function_exists('imagegif')
|
||||
&& (imagetypes() & IMG_GIF)
|
||||
) {
|
||||
// Do not use data:// wrapper to be independent of allow_url_fopen
|
||||
$imageResource = @imagecreatefromgif(__DIR__ . '/../../Resources/Public/Images/TestInput/Test.gif');
|
||||
if ($this->checkImageResource($imageResource)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP GD library has gif support'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD is loaded, but calling imagecreatefromgif() fails. This must be fixed, TYPO3 CMS won\'t work well otherwise.',
|
||||
'PHP GD library gif support broken',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD must be compiled with gif support. This is essential for TYPO3 CMS to work properly.',
|
||||
'PHP GD library gif support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check jpg support of GD library
|
||||
*/
|
||||
protected function checkGdLibJpgSupport()
|
||||
{
|
||||
if (function_exists('imagecreatefromjpeg')
|
||||
&& function_exists('imagejpeg')
|
||||
&& (imagetypes() & IMG_JPG)
|
||||
) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP GD library has jpg support'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD must be compiled with jpg support. This is essential for TYPO3 CMS to work properly.',
|
||||
'PHP GD library jpg support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check png support of GD library
|
||||
*/
|
||||
protected function checkGdLibPngSupport()
|
||||
{
|
||||
if (function_exists('imagecreatefrompng')
|
||||
&& function_exists('imagepng')
|
||||
&& (imagetypes() & IMG_PNG)
|
||||
) {
|
||||
// Do not use data:// wrapper to be independent of allow_url_fopen
|
||||
$imageResource = @imagecreatefrompng(__DIR__ . '/../../Resources/Public/Images/TestInput/Test.png');
|
||||
if ($this->checkImageResource($imageResource)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP GD library has png support'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD is compiled with png support, but calling imagecreatefrompng() fails.'
|
||||
. ' Check your environment and fix it, png in GD lib is important'
|
||||
. ' for TYPO3 CMS to work properly.',
|
||||
'PHP GD library png support broken',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'GD must be compiled with png support. This is essential for TYPO3 CMS to work properly',
|
||||
'PHP GD library png support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check gdlib supports freetype
|
||||
*/
|
||||
protected function checkGdLibFreeTypeSupport()
|
||||
{
|
||||
if (function_exists('imagettftext')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'There is a difference between the font size setting which the GD'
|
||||
. ' library should be supplied with. If installation is completed'
|
||||
. ' a test in the install tool helps to find out the value you need.',
|
||||
'PHP GD library has freetype font support'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Some core functionality and extension rely on the GD'
|
||||
. ' to render fonts on images. This support is missing'
|
||||
. ' in your environment. Install it.',
|
||||
'PHP GD library freetype support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate a given IP address.
|
||||
*
|
||||
* @param string $ip IP address to be tested
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidIp($ip)
|
||||
{
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this instance runs on windows OS
|
||||
*
|
||||
* @return bool TRUE if operating system is windows
|
||||
*/
|
||||
protected function isWindowsOs()
|
||||
{
|
||||
$windowsOs = false;
|
||||
if (stripos(PHP_OS, 'darwin') === false && stripos(PHP_OS, 'win') !== false) {
|
||||
$windowsOs = true;
|
||||
}
|
||||
return $windowsOs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the bytes value from a measurement string like "100k".
|
||||
*
|
||||
* @param string $measurement The measurement (e.g. "100k")
|
||||
* @return int The bytes value (e.g. 102400)
|
||||
*/
|
||||
protected function getBytesFromSizeMeasurement($measurement)
|
||||
{
|
||||
$bytes = (float)$measurement;
|
||||
if (stripos($measurement, 'G')) {
|
||||
$bytes *= 1024 * 1024 * 1024;
|
||||
} elseif (stripos($measurement, 'M')) {
|
||||
$bytes *= 1024 * 1024;
|
||||
} elseif (stripos($measurement, 'K')) {
|
||||
$bytes *= 1024;
|
||||
}
|
||||
return (int)$bytes;
|
||||
}
|
||||
|
||||
private function checkImageResource($imageResource): bool
|
||||
{
|
||||
return $imageResource instanceof \GdImage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Install\SystemEnvironment;
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
|
||||
/**
|
||||
* Check system environment status
|
||||
*
|
||||
* This interface needs to be implemented by hardcoded requirement
|
||||
* checks of the underlying server and PHP system.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use
|
||||
* plain text only. The return values of this class can be used
|
||||
* in different scopes (eg. as json array).
|
||||
*/
|
||||
interface CheckInterface
|
||||
{
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue;
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?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\Install\SystemEnvironment;
|
||||
|
||||
use Doctrine\DBAL\Driver\IBMDB2\Driver as DB2Driver;
|
||||
use Doctrine\DBAL\Driver\Mysqli\Driver as DoctrineMysqliDriver;
|
||||
use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineOCI8Driver;
|
||||
use Doctrine\DBAL\Driver\PDO\MySQL\Driver as DoctrinePDOMySqlDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\OCI\Driver as DoctrinePDOOCIDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as DoctrinePDOPgSqlDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\SQLite\Driver as DoctrinePDOSqliteDriver;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Install\Exception;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\Mysqli as DatabaseCheckDriverMysqli;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\PdoMysql as DatabaseCheckDriverPdoMysql;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\PDOPgSql as DatabaseCheckDriverPDOPgSql;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\PDOSqlite as DatabaseCheckDriverPDOSqlite;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform\MySql as DatabaseCheckPlatformMysql;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform\PostgreSql as DatabaseCheckPlatformPostgreSql;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform\Sqlite as DatabaseCheckPlatformSqlite;
|
||||
|
||||
/**
|
||||
* Check database configuration status
|
||||
*
|
||||
* This class is a hardcoded requirement check for the database server.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* The database requirements checks are separated into driver specific and / or more general requirements
|
||||
* specific for each DBMS platform.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The driver pdo_mysql requires a different set of checks, then the mysqli
|
||||
* driver (it requires other extensions to be loaded by PHP, configuration of that extension, etc.).
|
||||
* Those specific checks could be covered in a driver specific check like follows:
|
||||
*
|
||||
* - Create a new class in typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck/Driver
|
||||
* - It must extend TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\AbstractDriver and implement all methods
|
||||
* - Finally it has to be registered in TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck
|
||||
*
|
||||
* If the requirements are more general for the platform (e.g. MySQL, PostgreSQL, etc.),
|
||||
* they should be placed in the platform specific checks and fulfill those requirements:
|
||||
*
|
||||
* - Create a new class in typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck/Platform
|
||||
* - It must extend TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform\AbstractPlatform and implement all methods
|
||||
* - Finally it has to be registered in TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class DatabaseCheck implements CheckInterface
|
||||
{
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
private $messageQueue;
|
||||
|
||||
/**
|
||||
* List of database platforms to check
|
||||
*
|
||||
* @var string[]
|
||||
* @todo Check if this property can be removed after DatabaseCheck::retrieveDatabasePlatformByDriverName() could be
|
||||
* removed.
|
||||
*/
|
||||
private static $databaseDriverToPlatformMapping = [
|
||||
DoctrineMysqliDriver::class => DatabaseCheckPlatformMysql::class,
|
||||
DoctrinePDOMySqlDriver::class => DatabaseCheckPlatformMysql::class,
|
||||
DoctrinePDOPgSqlDriver::class => DatabaseCheckPlatformPostgreSql::class,
|
||||
DoctrinePDOSqliteDriver::class => DatabaseCheckPlatformSqlite::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $driverMap = [
|
||||
'pdo_mysql' => DoctrinePDOMySqlDriver::class,
|
||||
'pdo_sqlite' => DoctrinePDOSqliteDriver::class,
|
||||
'pdo_pgsql' => DoctrinePDOPgSqlDriver::class,
|
||||
'pdo_oci' => DoctrinePDOOCIDriver::class,
|
||||
'oci8' => DoctrineOCI8Driver::class,
|
||||
'ibm_db2' => DB2Driver::class,
|
||||
'mysqli' => DoctrineMysqliDriver::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of database driver to check
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $databaseDriverCheckMap = [
|
||||
DoctrineMysqliDriver::class => DatabaseCheckDriverMysqli::class,
|
||||
DoctrinePDOMySqlDriver::class => DatabaseCheckDriverPdoMysql::class,
|
||||
DoctrinePDOPgSqlDriver::class => DatabaseCheckDriverPDOPgSql::class,
|
||||
DoctrinePDOSqliteDriver::class => DatabaseCheckDriverPDOSqlite::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private ?ConnectionPool $connectionPool = null,
|
||||
) {
|
||||
$this->messageQueue = new FlashMessageQueue('install-database-check');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of each database platform identified to be installed on the system
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$installedDrivers = $this->identifyInstalledDatabaseDriver();
|
||||
|
||||
// check requirements of database platform for installed driver
|
||||
foreach ($installedDrivers as $driver) {
|
||||
try {
|
||||
$this->checkDatabasePlatformRequirements($driver);
|
||||
} catch (Exception $exception) {
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'',
|
||||
$exception->getMessage(),
|
||||
ContextualFeedbackSeverity::INFO
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check requirements of database driver for installed driver
|
||||
foreach ($installedDrivers as $driver) {
|
||||
try {
|
||||
$this->checkDatabaseDriverRequirements($driver);
|
||||
} catch (Exception $exception) {
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'',
|
||||
$exception->getMessage(),
|
||||
ContextualFeedbackSeverity::INFO
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
public function checkDatabaseDriverRequirements(string $databaseDriver): FlashMessageQueue
|
||||
{
|
||||
if (!empty($this->databaseDriverCheckMap[$databaseDriver])) {
|
||||
/** @var CheckInterface $databaseDriverCheck */
|
||||
$databaseDriverCheck = new $this->databaseDriverCheckMap[$databaseDriver]();
|
||||
foreach ($databaseDriverCheck->getStatus() as $message) {
|
||||
$this->messageQueue->addMessage($message);
|
||||
}
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'There are no database driver checks available for the given database driver: %s',
|
||||
$databaseDriver
|
||||
),
|
||||
1572521099
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of a specific database platform
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkDatabasePlatformRequirements(string $databaseDriver): FlashMessageQueue
|
||||
{
|
||||
static $checkedPlatform = [];
|
||||
$databasePlatformClass = self::$databaseDriverToPlatformMapping[$databaseDriver];
|
||||
|
||||
// execute platform checks only once
|
||||
if (in_array($databasePlatformClass, $checkedPlatform, true)) {
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
if ($this->connectionPool === null) {
|
||||
// Skip checks as we can not check as long as connections are not established yet.
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
if (!empty(self::$databaseDriverToPlatformMapping[$databaseDriver])) {
|
||||
$platformMessageQueue = (new $databasePlatformClass($this->connectionPool))->getStatus();
|
||||
foreach ($platformMessageQueue as $message) {
|
||||
$this->messageQueue->enqueue($message);
|
||||
}
|
||||
$checkedPlatform[] = $databasePlatformClass;
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'There are no database platform checks available for the given database driver: %s',
|
||||
$databaseDriver
|
||||
),
|
||||
1573753070
|
||||
);
|
||||
}
|
||||
|
||||
public function identifyInstalledDatabaseDriver(): array
|
||||
{
|
||||
$installedDrivers = [];
|
||||
|
||||
if (static::isMysqli()) {
|
||||
$installedDrivers[] = DoctrineMysqliDriver::class;
|
||||
}
|
||||
|
||||
if (static::isPdoMysql()) {
|
||||
$installedDrivers[] = DoctrinePDOMySqlDriver::class;
|
||||
}
|
||||
|
||||
if (static::isPdoPgsql()) {
|
||||
$installedDrivers[] = DoctrinePDOPgSqlDriver::class;
|
||||
}
|
||||
|
||||
if (static::isPdoSqlite()) {
|
||||
$installedDrivers[] = DoctrinePDOSqliteDriver::class;
|
||||
}
|
||||
|
||||
return $installedDrivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @todo This method seems to be unused. Check if it can be removed or if it needs to be deprecated.
|
||||
*/
|
||||
public static function retrieveDatabasePlatformByDriverName(string $databaseDriverName): string
|
||||
{
|
||||
$databaseDriverClassName = static::retrieveDatabaseDriverClassByDriverName($databaseDriverName);
|
||||
if (!empty(self::$databaseDriverToPlatformMapping[$databaseDriverClassName])) {
|
||||
return self::$databaseDriverToPlatformMapping[$databaseDriverClassName];
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf('There is no database platform available for the given driver: %s', $databaseDriverName),
|
||||
1573753057
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function retrieveDatabaseDriverClassByDriverName(string $driverName): string
|
||||
{
|
||||
if (!empty(self::$driverMap[$driverName])) {
|
||||
return self::$driverMap[$driverName];
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf('There is no database driver available for the given driver name: %s', $driverName),
|
||||
1573740447
|
||||
);
|
||||
}
|
||||
|
||||
public function getMessageQueue(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
public static function isMysqli(): bool
|
||||
{
|
||||
return extension_loaded('mysqli');
|
||||
}
|
||||
|
||||
public static function isPdoMysql(): bool
|
||||
{
|
||||
return extension_loaded('pdo_mysql');
|
||||
}
|
||||
|
||||
public static function isPdoPgsql(): bool
|
||||
{
|
||||
return extension_loaded('pdo_pgsql');
|
||||
}
|
||||
|
||||
public static function isPdoSqlite(): bool
|
||||
{
|
||||
return extension_loaded('pdo_sqlite');
|
||||
}
|
||||
}
|
||||
@@ -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\Install\SystemEnvironment\DatabaseCheck\Driver;
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\Check;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
|
||||
|
||||
/**
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
abstract class AbstractDriver implements CheckInterface
|
||||
{
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->messageQueue = new FlashMessageQueue('install-database-check-driver');
|
||||
}
|
||||
|
||||
public function getMessageQueue(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the required PHP extensions for this database platform
|
||||
* @param string $extension PHP extension name to check
|
||||
*/
|
||||
protected function checkPhpExtensions(string $extension): void
|
||||
{
|
||||
$systemEnvironmentCheck = GeneralUtility::makeInstance(Check::class);
|
||||
$systemEnvironmentCheck->checkPhpExtension($extension);
|
||||
|
||||
foreach ($systemEnvironmentCheck->getMessageQueue() as $message) {
|
||||
$this->messageQueue->addMessage($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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\Install\SystemEnvironment\DatabaseCheck\Driver;
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* Check database configuration status for MySQLi driver
|
||||
*
|
||||
* This class is a hardcoded requirement check for the database driver.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Mysqli extends AbstractDriver
|
||||
{
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$this->checkPhpExtensions('mysqli');
|
||||
$this->checkMysqliReconnectSetting();
|
||||
|
||||
return $this->getMessageQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that mysqli.reconnect is set to 0 in order to avoid improper reconnects
|
||||
*/
|
||||
public function checkMysqliReconnectSetting()
|
||||
{
|
||||
$currentMysqliReconnectSetting = ini_get('mysqli.reconnect');
|
||||
if ($currentMysqliReconnectSetting === '1') {
|
||||
$this->getMessageQueue()->enqueue(new FlashMessage(
|
||||
'mysqli.reconnect=1' . LF
|
||||
. 'PHP is configured to automatically reconnect the database connection on disconnection.' . LF
|
||||
. ' Warning: If (e.g. during a long-running task) the connection is dropped and automatically reconnected, '
|
||||
. ' it may not be reinitialized properly (e.g. charset) and write mangled data to the database!',
|
||||
'PHP mysqli.reconnect is enabled',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->getMessageQueue()->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PHP mysqli.reconnect is fine'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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\Install\SystemEnvironment\DatabaseCheck\Driver;
|
||||
|
||||
/**
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PDOPgSql extends AbstractDriver {}
|
||||
@@ -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\Install\SystemEnvironment\DatabaseCheck\Driver;
|
||||
|
||||
/**
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PDOSqlite extends AbstractDriver {}
|
||||
@@ -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\Install\SystemEnvironment\DatabaseCheck\Driver;
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
|
||||
/**
|
||||
* Check database configuration status for PDOMySql driver
|
||||
*
|
||||
* This class is a hardcoded requirement check for the database driver.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PdoMysql extends AbstractDriver
|
||||
{
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$this->checkPhpExtensions('pdo_mysql');
|
||||
|
||||
return $this->getMessageQueue();
|
||||
}
|
||||
}
|
||||
@@ -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\Install\SystemEnvironment\DatabaseCheck\Platform;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
abstract class AbstractPlatform implements PlatformCheckInterface
|
||||
{
|
||||
/**
|
||||
* @var int The maximum length of the schema name
|
||||
*/
|
||||
protected const SCHEMA_NAME_MAX_LENGTH = 64;
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
public function __construct(
|
||||
protected readonly ConnectionPool $connectionPool
|
||||
) {
|
||||
$this->messageQueue = new FlashMessageQueue('install-database-check-platform');
|
||||
}
|
||||
|
||||
public function getMessageQueue(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the database name
|
||||
*/
|
||||
public static function isValidDatabaseName(string $databaseName): bool
|
||||
{
|
||||
return strlen($databaseName) <= static::SCHEMA_NAME_MAX_LENGTH && preg_match('/^[a-zA-Z0-9\$_]*$/', $databaseName);
|
||||
}
|
||||
|
||||
protected function checkDatabaseName(Connection $connection): void
|
||||
{
|
||||
if (static::isValidDatabaseName((string)$connection->getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'The given database name must not be longer than ' . static::SCHEMA_NAME_MAX_LENGTH . ' characters'
|
||||
. ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)'
|
||||
. ' and underscores (_).',
|
||||
'Database name not valid',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?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\Install\SystemEnvironment\DatabaseCheck\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* Check database configuration status for MySQL server
|
||||
*
|
||||
* This class is a hardcoded requirement check for the database server.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MySql extends AbstractPlatform
|
||||
{
|
||||
protected const PLATFORM_MYSQL = 'mysql';
|
||||
protected const PLATFORM_MARIADB = 'mariadb';
|
||||
|
||||
/**
|
||||
* Minimum supported MySQL version
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $minimumVersion = [
|
||||
self::PLATFORM_MYSQL => '8.0.17',
|
||||
self::PLATFORM_MARIADB => '10.4.3',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $platformLabel = [
|
||||
self::PLATFORM_MYSQL => 'MySQL',
|
||||
self::PLATFORM_MARIADB => 'MariaDB',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of MySQL modes that are incompatible with TYPO3 CMS
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $incompatibleSqlModes = [
|
||||
'NO_BACKSLASH_ESCAPES',
|
||||
];
|
||||
|
||||
/**
|
||||
* Charset of the database that should be fulfilled
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseCharsetToCheck = [
|
||||
'utf8',
|
||||
'utf8mb3',
|
||||
'utf8mb4',
|
||||
];
|
||||
|
||||
/**
|
||||
* Charset of the database server that should be fulfilled
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseServerCharsetToCheck = [
|
||||
'utf8',
|
||||
'utf8mb3',
|
||||
'utf8mb4',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$defaultConnection = $this->connectionPool
|
||||
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
|
||||
$platform = $defaultConnection->getDatabasePlatform();
|
||||
if (!($platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform)) {
|
||||
return $this->messageQueue;
|
||||
}
|
||||
$this->checkMySQLOrMariaDBVersion($defaultConnection);
|
||||
$this->checkInvalidSqlModes($defaultConnection);
|
||||
$this->checkDefaultDatabaseCharset($defaultConnection);
|
||||
$this->checkDefaultDatabaseServerCharset($defaultConnection);
|
||||
$this->checkDatabaseName($defaultConnection);
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any SQL mode is set which is not compatible with TYPO3
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
protected function checkInvalidSqlModes(Connection $connection)
|
||||
{
|
||||
$detectedIncompatibleSqlModes = $this->getIncompatibleSqlModes($connection);
|
||||
if (!empty($detectedIncompatibleSqlModes)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Incompatible SQL modes have been detected:'
|
||||
. ' ' . implode(', ', $detectedIncompatibleSqlModes) . '.'
|
||||
. ' The listed modes are not compatible with TYPO3 CMS.'
|
||||
. ' You have to change that setting in your MySQL environment'
|
||||
. ' or in $GLOBALS[\'TYPO3_CONF_VARS\'][\'DB\'][\'Connections\'][\'Default\'][\'initCommands\']',
|
||||
'Incompatible SQL modes found!',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'No incompatible SQL modes found.'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check minimum MySQL version
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
protected function checkMySQLOrMariaDBVersion(Connection $connection): void
|
||||
{
|
||||
$platformLabel = $this->getPlatformLabel($connection);
|
||||
$minimumVersion = $this->getMinimumVersion($connection);
|
||||
$serverVersion = $connection->getPlatformServerVersion();
|
||||
preg_match('/MySQL (5\.5\.5-|)((\d+\.)*(\d+\.)*\d+)/', $serverVersion, $match);
|
||||
$currentMysqlVersion = $match[2] ?? null;
|
||||
if ($currentMysqlVersion === null) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your ' . $platformLabel . ' version could not be determined. Verify manually to have at least '
|
||||
. $platformLabel . ' ' . $minimumVersion . ' installed. Version value: ' . $serverVersion,
|
||||
$platformLabel . ' version invalid',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} elseif (version_compare($currentMysqlVersion, $minimumVersion, '<')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your ' . $platformLabel . ' version ' . $currentMysqlVersion . ' is too old. TYPO3 CMS does not run'
|
||||
. ' with this version. Update to at least ' . $platformLabel . ' ' . $minimumVersion,
|
||||
$platformLabel . ' version too low',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
$platformLabel . ' version ' . $currentMysqlVersion . ' is fine'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the database and reports an error if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseCharset(Connection $connection): void
|
||||
{
|
||||
$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())
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
$platformLabel = $this->getPlatformLabel($connection);
|
||||
if (!in_array($defaultDatabaseCharset, $this->databaseCharsetToCheck, true)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
sprintf(
|
||||
'Checking database character set failed, got key "%s" instead of "%s"',
|
||||
$defaultDatabaseCharset,
|
||||
implode(' or ', $this->databaseCharsetToCheck)
|
||||
),
|
||||
$platformLabel . ' database character set check failed',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
sprintf('%s database uses %s. All good.', $platformLabel, implode(' or ', $this->databaseCharsetToCheck))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the current sql mode settings
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
* @return array Contains all configured SQL modes that are incompatible
|
||||
*/
|
||||
protected function getIncompatibleSqlModes(Connection $connection): array
|
||||
{
|
||||
$sqlModes = explode(',', (string)$connection->executeQuery('SELECT @@SESSION.sql_mode;')->fetchOne());
|
||||
return array_intersect($this->incompatibleSqlModes, $sqlModes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the database server and reports an info if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseServerCharset(Connection $connection): void
|
||||
{
|
||||
$defaultServerCharset = $connection->executeQuery('SHOW VARIABLES LIKE \'character_set_server\'')->fetchAssociative();
|
||||
$platformLabel = $this->getPlatformLabel($connection);
|
||||
if (!in_array($defaultServerCharset['Value'], $this->databaseServerCharsetToCheck, true)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
sprintf(
|
||||
'Checking server character set failed, got key "%s" instead of "%s"',
|
||||
$defaultServerCharset['Value'],
|
||||
implode(' or ', $this->databaseServerCharsetToCheck)
|
||||
),
|
||||
$platformLabel . ' database server character set check failed',
|
||||
ContextualFeedbackSeverity::INFO
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
sprintf('%s server default uses %s. All good.', $platformLabel, implode(' or ', $this->databaseCharsetToCheck))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the database name
|
||||
*/
|
||||
public static function isValidDatabaseName(string $databaseName): bool
|
||||
{
|
||||
return strlen($databaseName) <= static::SCHEMA_NAME_MAX_LENGTH && preg_match('/^[\x{0001}-\x{FFFF}]*$/u', $databaseName);
|
||||
}
|
||||
|
||||
protected function checkDatabaseName(Connection $connection): void
|
||||
{
|
||||
if (static::isValidDatabaseName((string)$connection->getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'The given database name must not be longer than ' . static::SCHEMA_NAME_MAX_LENGTH . ' characters'
|
||||
. ' and consist of the Unicode Basic Multilingual Plane (BMP), except U+0000',
|
||||
'Database name not valid',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function getMinimumVersion(Connection $connection): string
|
||||
{
|
||||
return $this->minimumVersion[$this->getPlatformType($connection)];
|
||||
}
|
||||
|
||||
protected function getPlatformType(Connection $connection): string
|
||||
{
|
||||
return $this->isMariaDb($connection) ? self::PLATFORM_MARIADB : self::PLATFORM_MYSQL;
|
||||
}
|
||||
|
||||
protected function getPlatformLabel(Connection $connection): string
|
||||
{
|
||||
return $this->platformLabel[$this->getPlatformType($connection)];
|
||||
}
|
||||
|
||||
protected function isMariaDb(Connection $connection): bool
|
||||
{
|
||||
return $connection->getDatabasePlatform() instanceof DoctrineMariaDBPlatform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
|
||||
|
||||
/**
|
||||
* @internal This interface is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
interface PlatformCheckInterface extends CheckInterface
|
||||
{
|
||||
/**
|
||||
* Checks the character set of the database and reports an error if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseCharset(Connection $connection): void;
|
||||
|
||||
/**
|
||||
* Checks the character set of the database server and reports an info if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseServerCharset(Connection $connection): void;
|
||||
|
||||
public static function isValidDatabaseName(string $databaseName): bool;
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
<?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\Install\SystemEnvironment\DatabaseCheck\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* Check database configuration status for PostgreSQL
|
||||
*
|
||||
* This class is a hardcoded requirement check for the database server.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PostgreSql extends AbstractPlatform
|
||||
{
|
||||
/**
|
||||
* Minimum supported PostgreSQL Server version
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minimumPostgreSQLVerion = '10.0';
|
||||
|
||||
/**
|
||||
* Minimum supported libpq version
|
||||
* @var string
|
||||
*/
|
||||
protected $minimumLibPQVersion = '10.0';
|
||||
|
||||
/**
|
||||
* Charset of the database that should be fulfilled
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseCharsetToCheck = [
|
||||
'utf8',
|
||||
];
|
||||
|
||||
/**
|
||||
* Charset of the database server that should be fulfilled
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseServerCharsetToCheck = [
|
||||
'utf8',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$defaultConnection = $this->connectionPool
|
||||
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
|
||||
$platform = $defaultConnection->getDatabasePlatform();
|
||||
if (!($platform instanceof DoctrinePostgreSQLPlatform)) {
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
$this->checkPostgreSqlVersion($defaultConnection);
|
||||
$this->checkLibpqVersion();
|
||||
$this->checkDefaultDatabaseCharset($defaultConnection);
|
||||
$this->checkDefaultDatabaseServerCharset($defaultConnection);
|
||||
$this->checkDatabaseName($defaultConnection);
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check minimum PostgreSQL version
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
protected function checkPostgreSqlVersion(Connection $connection)
|
||||
{
|
||||
preg_match('/PostgreSQL ((\d+\.)*(\d+\.)*\d+)/', $connection->getPlatformServerVersion(), $match);
|
||||
$currentPostgreSqlVersion = $match[1];
|
||||
if (version_compare($currentPostgreSqlVersion, $this->minimumPostgreSQLVerion, '<')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your PostgreSQL version ' . $currentPostgreSqlVersion . ' is not supported. TYPO3 CMS does not run'
|
||||
. ' with this version. The minimum supported PostgreSQL version is ' . $this->minimumPostgreSQLVerion,
|
||||
'PostgreSQL Server version is unsupported',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PostgreSQL Server version is supported'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the version of ligpq within the PostgreSQL driver
|
||||
*/
|
||||
protected function checkLibpqVersion()
|
||||
{
|
||||
if (!defined('PGSQL_LIBPQ_VERSION')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'It is not possible to retrieve your PostgreSQL libpq version. Please check the version'
|
||||
. ' in the "phpinfo" area of the "System environment" module in the install tool manually.'
|
||||
. ' This should be found in section "pdo_pgsql".'
|
||||
. ' You should have at least the following version of PostgreSQL libpq installed: '
|
||||
. $this->minimumLibPQVersion,
|
||||
'PostgreSQL libpq version cannot be determined',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
preg_match('/((\d+\.)*(\d+\.)*\d+)/', \PGSQL_LIBPQ_VERSION, $match);
|
||||
$currentPostgreSqlLibpqVersion = $match[1];
|
||||
|
||||
if (version_compare($currentPostgreSqlLibpqVersion, $this->minimumLibPQVersion, '<')) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Your PostgreSQL libpq version "' . $currentPostgreSqlLibpqVersion . '" is unsupported.'
|
||||
. ' TYPO3 CMS does not run with this version. The minimum supported libpq version is '
|
||||
. $this->minimumLibPQVersion,
|
||||
'PostgreSQL libpq version is unsupported',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'PostgreSQL libpq version is supported'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the database and reports an error if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseCharset(Connection $connection): void
|
||||
{
|
||||
$defaultDatabaseCharset = $connection->executeQuery(
|
||||
'SELECT pg_catalog.pg_encoding_to_char(pg_database.encoding) from pg_database where datname = ?',
|
||||
[$connection->getDatabase()],
|
||||
[Connection::PARAM_STR]
|
||||
)->fetchAssociative();
|
||||
|
||||
if (!in_array(strtolower($defaultDatabaseCharset['pg_encoding_to_char']), $this->databaseCharsetToCheck, true)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
sprintf(
|
||||
'Checking database character set failed, got key "%s" instead of "%s"',
|
||||
$defaultDatabaseCharset['pg_encoding_to_char'],
|
||||
implode(' or ', $this->databaseCharsetToCheck)
|
||||
),
|
||||
'PostgreSQL database character set check failed',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
sprintf('PostgreSQL database uses %s. All good.', implode(' or ', $this->databaseCharsetToCheck))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the database server and reports an info if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseServerCharset(Connection $connection): void
|
||||
{
|
||||
$defaultServerCharset = $connection->executeQuery('SHOW SERVER_ENCODING')->fetchAssociative();
|
||||
|
||||
if (!in_array(strtolower($defaultServerCharset['server_encoding']), $this->databaseCharsetToCheck, true)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
sprintf(
|
||||
'Checking server character set failed, got key "%s" instead of "%s"',
|
||||
$defaultServerCharset['server_encoding'],
|
||||
implode(' or ', $this->databaseServerCharsetToCheck)
|
||||
),
|
||||
'PostgreSQL database character set check failed',
|
||||
ContextualFeedbackSeverity::INFO
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
sprintf('PostgreSQL server default uses %s. All good.', implode(' or ', $this->databaseCharsetToCheck))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the database name
|
||||
*/
|
||||
public static function isValidDatabaseName(string $databaseName): bool
|
||||
{
|
||||
return strlen($databaseName) <= static::SCHEMA_NAME_MAX_LENGTH && preg_match('/^(?!pg_)[a-zA-Z0-9\$_]*$/', $databaseName);
|
||||
}
|
||||
|
||||
protected function checkDatabaseName(Connection $connection): void
|
||||
{
|
||||
if (static::isValidDatabaseName((string)$connection->getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'The given database name must not be longer than ' . static::SCHEMA_NAME_MAX_LENGTH . ' characters'
|
||||
. ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)'
|
||||
. ' and underscores (_) and does not start with "pg_".',
|
||||
'Database name not valid',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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\Install\SystemEnvironment\DatabaseCheck\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Sqlite extends AbstractPlatform
|
||||
{
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$defaultConnection = $this->connectionPool
|
||||
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
|
||||
$platform = $defaultConnection->getDatabasePlatform();
|
||||
if (!($platform instanceof DoctrineSQLitePlatform)) {
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
$this->checkDefaultDatabaseCharset($defaultConnection);
|
||||
$this->checkDefaultDatabaseServerCharset($defaultConnection);
|
||||
$this->checkDatabaseName($defaultConnection);
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
/**
|
||||
* Checks the character set of the database and reports an error if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseCharset(Connection $connection): void
|
||||
{
|
||||
// TODO: Implement getDefaultDatabaseCharset() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the database server and reports an info if it is not utf-8.
|
||||
*
|
||||
* @param Connection $connection to the database to be checked
|
||||
*/
|
||||
public function checkDefaultDatabaseServerCharset(Connection $connection): void
|
||||
{
|
||||
// TODO: Implement getDefaultDatabaseServerCharset() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the database name
|
||||
* SQLite does not have any limitation for the length of the database name,
|
||||
* but must start with a letter or _
|
||||
*/
|
||||
public static function isValidDatabaseName(string $databaseName): bool
|
||||
{
|
||||
return (bool)preg_match('/^[A-Za-z_\/][a-zA-Z0-9\$\/_.-]*$/', $databaseName);
|
||||
}
|
||||
|
||||
protected function checkDatabaseName(Connection $connection): void
|
||||
{
|
||||
if (static::isValidDatabaseName((string)$connection->getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'The given database name must consist solely of basic latin letters (a-z), digits (0-9)'
|
||||
. ' and underscores (_).',
|
||||
'Database name not valid',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
/**
|
||||
* Evaluates a Content-Security-Policy HTTP header.
|
||||
*
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class ContentSecurityPolicyDirective
|
||||
{
|
||||
protected const RULE_PATTERN = '#(?:\'(?<instruction>[^\']+)\')|(?<source>[^\s]+)#';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $instructions = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $sources = [];
|
||||
|
||||
public function __construct(string $name, string $rule)
|
||||
{
|
||||
$this->name = $name;
|
||||
if (preg_match_all(self::RULE_PATTERN, $rule, $matches)) {
|
||||
foreach (array_keys($matches[0]) as $index) {
|
||||
if ($matches['instruction'][$index] !== '') {
|
||||
$this->instructions[] = $matches['instruction'][$index];
|
||||
} elseif ($matches['source'][$index] !== '') {
|
||||
$this->sources[] = $matches['source'][$index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getInstructions(): array
|
||||
{
|
||||
return $this->instructions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSources(): array
|
||||
{
|
||||
return $this->sources;
|
||||
}
|
||||
|
||||
public function hasInstructions(string ...$instructions): bool
|
||||
{
|
||||
return array_intersect($this->instructions, $instructions) !== [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
/**
|
||||
* Evaluates a Content-Security-Policy HTTP header.
|
||||
*
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class ContentSecurityPolicyHeader
|
||||
{
|
||||
protected const HEADER_PATTERN = '#(?<directive>default-src|script-src|style-src|object-src)\h+(?<rule>[^;]+)(?:\s*;\s*|$)#';
|
||||
|
||||
/**
|
||||
* @var ContentSecurityPolicyDirective[]
|
||||
*/
|
||||
protected $directives = [];
|
||||
|
||||
public function __construct(string $header)
|
||||
{
|
||||
if (preg_match_all(self::HEADER_PATTERN, $header, $matches)) {
|
||||
foreach ($matches['directive'] as $index => $name) {
|
||||
$this->directives[$name] = new ContentSecurityPolicyDirective(
|
||||
$name,
|
||||
$matches['rule'][$index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->directives);
|
||||
}
|
||||
|
||||
public function mitigatesCrossSiteScripting(?string $fileName = null): bool
|
||||
{
|
||||
$isSvg = str_ends_with($fileName ?? '', '.svg');
|
||||
$defaultSrc = isset($this->directives['default-src'])
|
||||
? $this->directiveMitigatesCrossSiteScripting($this->directives['default-src'])
|
||||
: null;
|
||||
$scriptSrc = isset($this->directives['script-src'])
|
||||
? $this->directiveMitigatesCrossSiteScripting($this->directives['script-src'])
|
||||
: null;
|
||||
$styleSrc = isset($this->directives['style-src'])
|
||||
? $this->directiveMitigatesCrossSiteScripting($this->directives['style-src'])
|
||||
|| ($isSvg && $this->directives['style-src']->hasInstructions('unsafe-inline'))
|
||||
: null;
|
||||
$objectSrc = isset($this->directives['object-src'])
|
||||
? $this->directiveMitigatesCrossSiteScripting($this->directives['object-src'])
|
||||
: null;
|
||||
return ($scriptSrc ?? $defaultSrc ?? false)
|
||||
&& ($styleSrc ?? $defaultSrc ?? false)
|
||||
&& ($objectSrc ?? $defaultSrc ?? false);
|
||||
}
|
||||
|
||||
protected function directiveMitigatesCrossSiteScripting(ContentSecurityPolicyDirective $directive): bool
|
||||
{
|
||||
return $directive->hasInstructions('none')
|
||||
&& !$directive->hasInstructions('unsafe-eval', 'unsafe-inline');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Declares contents on server response expectations on a static file.
|
||||
*
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class FileDeclaration
|
||||
{
|
||||
public const FLAG_BUILD_HTML = 1;
|
||||
public const FLAG_BUILD_PHP = 2;
|
||||
public const FLAG_BUILD_SVG = 4;
|
||||
public const FLAG_BUILD_HTML_DOCUMENT = 64;
|
||||
public const FLAG_BUILD_SVG_DOCUMENT = 128;
|
||||
|
||||
/**
|
||||
* @var FileLocation
|
||||
*/
|
||||
protected $fileLocation;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fileName;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $fail;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $expectedContentType;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $unexpectedContentType;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $expectedContent;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $unexpectedContent;
|
||||
|
||||
/**
|
||||
* @var \Closure|null
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $buildFlags = self::FLAG_BUILD_HTML | self::FLAG_BUILD_HTML_DOCUMENT;
|
||||
|
||||
public function __construct(FileLocation $fileLocation, string $fileName, bool $fail = false)
|
||||
{
|
||||
$this->fileLocation = $fileLocation;
|
||||
$this->fileName = $fileName;
|
||||
$this->fail = $fail;
|
||||
}
|
||||
|
||||
public function buildContent(): string
|
||||
{
|
||||
$content = '';
|
||||
if ($this->buildFlags & self::FLAG_BUILD_HTML) {
|
||||
$content .= '<div>HTML content</div>';
|
||||
}
|
||||
if ($this->buildFlags & self::FLAG_BUILD_PHP) {
|
||||
// base64 encoded representation of 'PHP content'
|
||||
$content .= '<div><?php echo base64_decode(\'UEhQIGNvbnRlbnQ=\');?></div>';
|
||||
}
|
||||
if ($this->buildFlags & self::FLAG_BUILD_SVG) {
|
||||
$content .= '<text id="test" x="0" y="0">SVG content</text>';
|
||||
}
|
||||
if ($this->buildFlags & self::FLAG_BUILD_SVG_DOCUMENT) {
|
||||
return sprintf(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg">%s</svg>',
|
||||
$content
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
'<!DOCTYPE html><html lang="en"><body>%s</body></html>',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
public function matches(ResponseInterface $response): bool
|
||||
{
|
||||
return $this->getMismatches($response) === [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StatusMessage[]
|
||||
*/
|
||||
public function getMismatches(ResponseInterface $response): array
|
||||
{
|
||||
$mismatches = [];
|
||||
if ($this->handler instanceof \Closure) {
|
||||
$result = $this->handler->call($this, $this, $response);
|
||||
if ($result !== null) {
|
||||
$mismatches[] = $result;
|
||||
}
|
||||
return $mismatches;
|
||||
}
|
||||
|
||||
$body = (string)$response->getBody();
|
||||
$contentType = $response->getHeaderLine('content-type');
|
||||
if ($this->expectedContent !== null && !str_contains($body, $this->expectedContent)) {
|
||||
$mismatches[] = new StatusMessage(
|
||||
'content mismatch %s',
|
||||
$this->expectedContent,
|
||||
$body
|
||||
);
|
||||
}
|
||||
if ($this->unexpectedContent !== null && str_contains($body, $this->unexpectedContent)) {
|
||||
$mismatches[] = new StatusMessage(
|
||||
'unexpected content %s',
|
||||
$this->unexpectedContent,
|
||||
$body
|
||||
);
|
||||
}
|
||||
if ($this->expectedContentType !== null
|
||||
&& !str_starts_with($contentType . ';', $this->expectedContentType . ';')) {
|
||||
$mismatches[] = new StatusMessage(
|
||||
'content-type mismatch %s, got %s',
|
||||
$this->expectedContentType,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
if ($this->unexpectedContentType !== null
|
||||
&& str_starts_with($contentType . ';', $this->unexpectedContentType . ';')) {
|
||||
$mismatches[] = new StatusMessage(
|
||||
'unexpected content-type %s',
|
||||
$this->unexpectedContentType,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
return $mismatches;
|
||||
}
|
||||
|
||||
public function withExpectedContentType(string $contentType): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->expectedContentType = $contentType;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withUnexpectedContentType(string $contentType): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->unexpectedContentType = $contentType;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withExpectedContent(string $content): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->expectedContent = $content;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withUnexpectedContent(string $content): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->unexpectedContent = $content;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withHandler(\Closure $handler): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->handler = $handler;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withBuildFlags(int $buildFlags): self
|
||||
{
|
||||
$target = clone $this;
|
||||
$target->buildFlags = $buildFlags;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function getFileLocation(): FileLocation
|
||||
{
|
||||
return $this->fileLocation;
|
||||
}
|
||||
|
||||
public function getFileName(): string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
public function getUrl(ServerRequestInterface $request): string
|
||||
{
|
||||
return $this->fileLocation->getBaseUrl($request) . $this->fileName;
|
||||
}
|
||||
|
||||
public function shallFail(): bool
|
||||
{
|
||||
return $this->fail;
|
||||
}
|
||||
|
||||
public function getExpectedContentType(): ?string
|
||||
{
|
||||
return $this->expectedContentType;
|
||||
}
|
||||
|
||||
public function getUnexpectedContentType(): ?string
|
||||
{
|
||||
return $this->unexpectedContentType;
|
||||
}
|
||||
|
||||
public function getExpectedContent(): ?string
|
||||
{
|
||||
return $this->expectedContent;
|
||||
}
|
||||
|
||||
public function getUnexpectedContent(): ?string
|
||||
{
|
||||
return $this->unexpectedContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* References local file path and corresponding HTTP base URL
|
||||
*
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class FileLocation
|
||||
{
|
||||
protected string $filePath;
|
||||
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->filePath = Environment::getPublicPath() . $path;
|
||||
}
|
||||
|
||||
public function getFilePath(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
public function getBaseUrl(ServerRequestInterface $request): string
|
||||
{
|
||||
return $request->getAttribute('normalizedParams')->getRequestHost()
|
||||
. PathUtility::getAbsoluteWebPath($this->filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
<?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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use GuzzleHttp\Promise\Utils;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
|
||||
/**
|
||||
* Checks how use web server is interpreting static files concerning
|
||||
* their `content-type` and evaluated content in HTTP responses.
|
||||
*
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class ServerResponseCheck implements CheckInterface
|
||||
{
|
||||
protected const WRAP_FLAT = 1;
|
||||
protected const WRAP_NESTED = 2;
|
||||
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
/**
|
||||
* @var FileLocation
|
||||
*/
|
||||
protected $assetLocation;
|
||||
|
||||
/**
|
||||
* @var FileLocation
|
||||
*/
|
||||
protected $fileadminLocation;
|
||||
|
||||
/**
|
||||
* @var FileDeclaration[]
|
||||
*/
|
||||
protected $fileDeclarations;
|
||||
|
||||
public function __construct(
|
||||
protected readonly UriBuilder $uriBuilder,
|
||||
protected readonly bool $useMarkup = true,
|
||||
) {
|
||||
$fileName = bin2hex(random_bytes(4));
|
||||
$folderName = bin2hex(random_bytes(4));
|
||||
$this->assetLocation = new FileLocation(sprintf('/typo3temp/assets/%s.tmp/', $folderName));
|
||||
$fileadminDir = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] ?? 'fileadmin', '/');
|
||||
$this->fileadminLocation = new FileLocation(sprintf('/%s/%s.tmp/', $fileadminDir, $folderName));
|
||||
$this->fileDeclarations = $this->initializeFileDeclarations($fileName);
|
||||
}
|
||||
|
||||
public function asStatus(ServerRequestInterface $request): Status
|
||||
{
|
||||
$messageQueue = $this->getStatus($request);
|
||||
$messages = [];
|
||||
foreach ($messageQueue->getAllMessages() as $flashMessage) {
|
||||
$messages[] = $flashMessage->getMessage();
|
||||
}
|
||||
$detailsLink = sprintf(
|
||||
'<p><a href="%s" rel="noreferrer" target="_blank">%s</a></p>',
|
||||
'https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/9.5.x/Feature-91354-IntegrateServerResponseSecurityChecks.html',
|
||||
'Please see documentation for further details...'
|
||||
);
|
||||
if ($messageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR) !== []) {
|
||||
$title = 'Potential vulnerabilities';
|
||||
$label = $detailsLink;
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
} elseif ($messageQueue->getAllMessages(ContextualFeedbackSeverity::WARNING) !== []) {
|
||||
$title = 'Warnings';
|
||||
$label = $detailsLink;
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
}
|
||||
return new Status(
|
||||
'Server Response',
|
||||
$title ?? 'OK',
|
||||
$this->wrapList($messages, $label ?? '', self::WRAP_NESTED),
|
||||
$severity ?? ContextualFeedbackSeverity::OK
|
||||
);
|
||||
}
|
||||
|
||||
public function getStatus(?ServerRequestInterface $request = null): FlashMessageQueue
|
||||
{
|
||||
if ($request === null) {
|
||||
throw new \RuntimeException('ServerResponseCheck requires a request', 1775761298);
|
||||
}
|
||||
$messageQueue = new FlashMessageQueue('install-server-response-check');
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$messageQueue->addMessage(
|
||||
new FlashMessage(
|
||||
'Skipped for PHP_SAPI=cli-server',
|
||||
'Checks skipped',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
)
|
||||
);
|
||||
return $messageQueue;
|
||||
}
|
||||
try {
|
||||
$this->buildFileDeclarations();
|
||||
$this->processHostCheck($messageQueue);
|
||||
$this->processFileDeclarations($messageQueue, $request);
|
||||
$this->finishMessageQueue($messageQueue);
|
||||
} finally {
|
||||
$this->purgeFileDeclarations();
|
||||
}
|
||||
return $messageQueue;
|
||||
}
|
||||
|
||||
protected function initializeFileDeclarations(string $fileName): array
|
||||
{
|
||||
$cspClosure = function (FileDeclaration $fileDeclaration, ResponseInterface $response): ?StatusMessage {
|
||||
$cspHeader = new ContentSecurityPolicyHeader(
|
||||
$response->getHeaderLine('content-security-policy')
|
||||
);
|
||||
|
||||
if ($cspHeader->isEmpty()) {
|
||||
return new StatusMessage(
|
||||
'missing Content-Security-Policy for this location'
|
||||
);
|
||||
}
|
||||
if (!$cspHeader->mitigatesCrossSiteScripting($fileDeclaration->getFileName())) {
|
||||
return new StatusMessage(
|
||||
'weak Content-Security-Policy for this location "%s"',
|
||||
$response->getHeaderLine('content-security-policy')
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return [
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.html'))
|
||||
->withExpectedContentType('text/html')
|
||||
->withExpectedContent('HTML content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.wrong'))
|
||||
->withUnexpectedContentType('text/html')
|
||||
->withExpectedContent('HTML content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.html.wrong'))
|
||||
->withUnexpectedContentType('text/html')
|
||||
->withExpectedContent('HTML content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.1.svg.wrong'))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_SVG | FileDeclaration::FLAG_BUILD_SVG_DOCUMENT)
|
||||
->withUnexpectedContentType('image/svg+xml')
|
||||
->withExpectedContent('SVG content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.2.svg.wrong'))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_SVG | FileDeclaration::FLAG_BUILD_SVG_DOCUMENT)
|
||||
->withUnexpectedContentType('image/svg')
|
||||
->withExpectedContent('SVG content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.php.wrong', true))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_PHP | FileDeclaration::FLAG_BUILD_HTML_DOCUMENT)
|
||||
->withUnexpectedContent('PHP content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.html.txt'))
|
||||
->withExpectedContentType('text/plain')
|
||||
->withUnexpectedContentType('text/html')
|
||||
->withExpectedContent('HTML content'),
|
||||
(new FileDeclaration($this->assetLocation, $fileName . '.php.txt', true))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_PHP | FileDeclaration::FLAG_BUILD_HTML_DOCUMENT)
|
||||
->withUnexpectedContent('PHP content'),
|
||||
(new FileDeclaration($this->fileadminLocation, $fileName . '.html'))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_HTML_DOCUMENT)
|
||||
->withHandler($cspClosure),
|
||||
(new FileDeclaration($this->fileadminLocation, $fileName . '.svg'))
|
||||
->withBuildFlags(FileDeclaration::FLAG_BUILD_SVG | FileDeclaration::FLAG_BUILD_SVG_DOCUMENT)
|
||||
->withHandler($cspClosure),
|
||||
];
|
||||
}
|
||||
|
||||
protected function buildFileDeclarations(): void
|
||||
{
|
||||
foreach ($this->fileDeclarations as $fileDeclaration) {
|
||||
$filePath = $fileDeclaration->getFileLocation()->getFilePath();
|
||||
if (!is_dir($filePath)) {
|
||||
GeneralUtility::mkdir_deep($filePath);
|
||||
}
|
||||
GeneralUtility::writeFile(
|
||||
$filePath . $fileDeclaration->getFileName(),
|
||||
$fileDeclaration->buildContent(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function purgeFileDeclarations(): void
|
||||
{
|
||||
GeneralUtility::rmdir($this->assetLocation->getFilePath(), true);
|
||||
GeneralUtility::rmdir($this->fileadminLocation->getFilePath(), true);
|
||||
}
|
||||
|
||||
protected function processHostCheck(FlashMessageQueue $messageQueue): void
|
||||
{
|
||||
$random = GeneralUtility::makeInstance(Random::class);
|
||||
$randomHost = $random->generateRandomHexString(10) . '.random.example.org';
|
||||
$time = (string)time();
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
$url = $this->uriBuilder->buildUriFromRoute(
|
||||
'install.server-response-check.host',
|
||||
['src-time' => $time, 'src-hash' => $hashService->hmac($time, 'server-response-check')],
|
||||
UriBuilder::ABSOLUTE_URL
|
||||
);
|
||||
try {
|
||||
$client = new Client(['timeout' => 10]);
|
||||
$response = $client->request('GET', (string)$url, [
|
||||
'headers' => ['Host' => $randomHost],
|
||||
'allow_redirects' => false,
|
||||
'verify' => false,
|
||||
]);
|
||||
} catch (TransferException $exception) {
|
||||
// it is expected that the previous request fails
|
||||
return;
|
||||
}
|
||||
// in case we end up here, the server processed an HTTP request with invalid HTTP host header
|
||||
$messageParts = [];
|
||||
$locationHeader = $response->getHeaderLine('location');
|
||||
if (!empty($locationHeader) && (new Uri($locationHeader))->getHost() === $randomHost) {
|
||||
$messageParts[] = sprintf('HTTP Location header contained unexpected "%s"', $randomHost);
|
||||
}
|
||||
$data = json_decode((string)$response->getBody(), true);
|
||||
$serverHttpHost = $data['server.HTTP_HOST'] ?? null;
|
||||
$serverServerName = $data['server.SERVER_NAME'] ?? null;
|
||||
if ($serverHttpHost === $randomHost) {
|
||||
$messageParts[] = sprintf('HTTP_HOST contained unexpected "%s"', $randomHost);
|
||||
}
|
||||
if ($serverServerName === $randomHost) {
|
||||
$messageParts[] = sprintf('SERVER_NAME contained unexpected "%s"', $randomHost);
|
||||
}
|
||||
if ($messageParts !== []) {
|
||||
$messageQueue->addMessage(
|
||||
new FlashMessage(
|
||||
$this->wrapList($messageParts, (string)$url, self::WRAP_FLAT),
|
||||
'Unexpected server response',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processFileDeclarations(FlashMessageQueue $messageQueue, ServerRequestInterface $request): void
|
||||
{
|
||||
$promises = [];
|
||||
$client = new Client(['timeout' => 10]);
|
||||
foreach ($this->fileDeclarations as $fileDeclaration) {
|
||||
$promises[] = $client->requestAsync('GET', $fileDeclaration->getUrl($request));
|
||||
}
|
||||
foreach (Utils::settle($promises)->wait() as $index => $response) {
|
||||
$fileDeclaration = $this->fileDeclarations[$index];
|
||||
if (($response['reason'] ?? null) instanceof BadResponseException) {
|
||||
$messageQueue->addMessage(
|
||||
new FlashMessage(
|
||||
sprintf(
|
||||
'(%d): %s',
|
||||
$response['reason']->getCode(),
|
||||
$response['reason']->getRequest()->getUri()
|
||||
),
|
||||
'HTTP warning',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!($response['value'] ?? null) instanceof ResponseInterface || $fileDeclaration->matches($response['value'])) {
|
||||
continue;
|
||||
}
|
||||
$messageQueue->addMessage(
|
||||
new FlashMessage(
|
||||
$this->createMismatchMessage($fileDeclaration, $response['value'], $request),
|
||||
'Unexpected server response',
|
||||
$fileDeclaration->shallFail() ? ContextualFeedbackSeverity::ERROR : ContextualFeedbackSeverity::WARNING
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function finishMessageQueue(FlashMessageQueue $messageQueue): void
|
||||
{
|
||||
if ($messageQueue->getAllMessages(ContextualFeedbackSeverity::WARNING) !== []
|
||||
|| $messageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR) !== []) {
|
||||
return;
|
||||
}
|
||||
$messageQueue->addMessage(
|
||||
new FlashMessage(
|
||||
sprintf('All %d files processed correctly', count($this->fileDeclarations)),
|
||||
'Expected server response',
|
||||
ContextualFeedbackSeverity::OK
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function createMismatchMessage(FileDeclaration $fileDeclaration, ResponseInterface $response, ServerRequestInterface $request): string
|
||||
{
|
||||
$messageParts = array_map(
|
||||
function (StatusMessage $mismatch): string {
|
||||
return vsprintf(
|
||||
$mismatch->getMessage(),
|
||||
$this->wrapValues($mismatch->getValues(), '<code>', '</code>')
|
||||
);
|
||||
},
|
||||
$fileDeclaration->getMismatches($response)
|
||||
);
|
||||
return $this->wrapList($messageParts, $fileDeclaration->getUrl($request), self::WRAP_FLAT);
|
||||
}
|
||||
|
||||
protected function wrapList(array $items, string $label, int $style): string
|
||||
{
|
||||
if (!$this->useMarkup) {
|
||||
return sprintf(
|
||||
'%s%s',
|
||||
$label ? $label . ': ' : '',
|
||||
implode(', ', $items)
|
||||
);
|
||||
}
|
||||
if ($style === self::WRAP_NESTED) {
|
||||
return sprintf(
|
||||
'%s<ul>%s</ul>',
|
||||
$label,
|
||||
implode('', $this->wrapItems($items, '<li>', '</li>'))
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
'<p>%s%s</p>',
|
||||
$label,
|
||||
implode('', $this->wrapItems($items, '<br>', ''))
|
||||
);
|
||||
}
|
||||
|
||||
protected function wrapItems(array $items, string $before, string $after): array
|
||||
{
|
||||
return array_map(
|
||||
function (string $item) use ($before, $after): string {
|
||||
return $before . $item . $after;
|
||||
},
|
||||
array_filter($items)
|
||||
);
|
||||
}
|
||||
|
||||
protected function wrapValues(array $values, string $before, string $after): array
|
||||
{
|
||||
return array_map(
|
||||
function (string $value) use ($before, $after): string {
|
||||
return $this->wrapValue($value, $before, $after);
|
||||
},
|
||||
array_filter($values)
|
||||
);
|
||||
}
|
||||
|
||||
protected function wrapValue(string $value, string $before, string $after): string
|
||||
{
|
||||
if ($this->useMarkup) {
|
||||
return $before . htmlspecialchars($value) . $after;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -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\Install\SystemEnvironment\ServerResponse;
|
||||
|
||||
/**
|
||||
* @internal should only be used from within TYPO3 Core
|
||||
*/
|
||||
class StatusMessage
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
public function __construct(protected readonly string $message, string ...$values)
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getValues(): array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Install\SystemEnvironment;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
|
||||
use TYPO3\CMS\Core\Service\OpcodeCacheService;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Check TYPO3 setup status
|
||||
*
|
||||
* This class is a hardcoded requirement check for the TYPO3 setup.
|
||||
*
|
||||
* The status messages and title *must not* include HTML, use plain
|
||||
* text only. The return values of this class are not bound to HTML
|
||||
* and can be used in different scopes (eg. as json array).
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class SetupCheck implements CheckInterface
|
||||
{
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
/**
|
||||
* Get all status information as array with status objects
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$this->messageQueue = new FlashMessageQueue('install');
|
||||
|
||||
$this->checkTrustedHostPattern();
|
||||
$this->checkDownloadsPossible();
|
||||
$this->checkSystemLocale();
|
||||
$this->checkLocaleWithUTF8filesystem();
|
||||
$this->checkSomePhpOpcodeCacheIsLoaded();
|
||||
$this->isTrueTypeFontWorking();
|
||||
|
||||
return $this->messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the trusted hosts pattern check
|
||||
*/
|
||||
protected function checkTrustedHostPattern()
|
||||
{
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Trusted hosts pattern is configured to allow all header values. Check the pattern defined in Admin'
|
||||
. ' Tools -> Settings -> Configure Installation-Wide Options -> System -> trustedHostsPattern'
|
||||
. ' and adapt it to expected host value(s).',
|
||||
'Trusted hosts pattern is insecure',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
} else {
|
||||
if (Environment::isCli()) {
|
||||
return;
|
||||
}
|
||||
$verifyHostHeader = new VerifyHostHeader($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '');
|
||||
if ($verifyHostHeader->isAllowedHostHeaderValue($_SERVER['HTTP_HOST'], $_SERVER)) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Trusted hosts pattern is configured to allow current host value.'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'The trusted hosts pattern will be configured to allow all header values. This is because your $SERVER_NAME:$SERVER_PORT'
|
||||
. ' is "' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . '" while your HTTP_HOST is "'
|
||||
. $_SERVER['HTTP_HOST'] . '", which can happen in specific proxy-routing setups.'
|
||||
. ' After installation, you may want to adapt the pattern defined in Admin'
|
||||
. ' Tools -> Settings -> Configure Installation-Wide Options -> System -> trustedHostsPattern'
|
||||
. ' to reflect the expected host value(s).',
|
||||
'Trusted hosts pattern mismatch',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is possible to download external data (e.g. TER)
|
||||
* Either allow_url_fopen must be enabled or curl must be used
|
||||
*/
|
||||
protected function checkDownloadsPossible()
|
||||
{
|
||||
$allowUrlFopen = (bool)ini_get('allow_url_fopen');
|
||||
$curlEnabled = function_exists('curl_version');
|
||||
if ($allowUrlFopen || $curlEnabled) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Fetching external URLs is allowed'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Either enable PHP runtime setting "allow_url_fopen"' . LF . 'or compile curl into your PHP with --with-curl.',
|
||||
'Fetching external URLs is not allowed',
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if systemLocale setting is correct (locale exists in the OS)
|
||||
*/
|
||||
protected function checkSystemLocale()
|
||||
{
|
||||
$currentLocale = (string)setlocale(LC_CTYPE, '0');
|
||||
|
||||
// On Windows an empty locale value uses the regional settings from the Control Panel
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] === '' && !Environment::isWindows()) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'$GLOBALS[TYPO3_CONF_VARS][SYS][systemLocale] is not set. This is fine as long as no UTF-8 file system is used.',
|
||||
'Empty systemLocale setting',
|
||||
ContextualFeedbackSeverity::INFO
|
||||
));
|
||||
} elseif (setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']) === false) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Current value of the $GLOBALS[TYPO3_CONF_VARS][SYS][systemLocale] is incorrect. A locale with'
|
||||
. ' this name doesn\'t exist in the operating system.',
|
||||
'Incorrect systemLocale setting',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
setlocale(LC_CTYPE, $currentLocale);
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'System locale is correct'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we can use file names with UTF-8 characters.
|
||||
* Configured system locale must support UTF-8 when UTF8filesystem is set
|
||||
*/
|
||||
protected function checkLocaleWithUTF8filesystem()
|
||||
{
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
|
||||
// On Windows an empty local value uses the regional settings from the Control Panel
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] === '' && !Environment::isWindows()) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'$GLOBALS[TYPO3_CONF_VARS][SYS][UTF8filesystem] is set, but $GLOBALS[TYPO3_CONF_VARS][SYS][systemLocale]'
|
||||
. ' is empty. Make sure a valid locale which supports UTF-8 is set.',
|
||||
'System locale not set on UTF-8 file system',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
} else {
|
||||
$testString = 'ÖöĄĆŻĘĆćążąęó.jpg';
|
||||
$currentLocale = (string)setlocale(LC_CTYPE, '0');
|
||||
$quote = Environment::isWindows() ? '"' : '\'';
|
||||
setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
|
||||
if (escapeshellarg($testString) === $quote . $testString . $quote) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'File names with UTF-8 characters can be used.'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Please check your $GLOBALS[TYPO3_CONF_VARS][SYS][systemLocale] setting.',
|
||||
'System locale setting doesn\'t support UTF-8 file names.',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
setlocale(LC_CTYPE, $currentLocale);
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Skipping test, as UTF8filesystem is not enabled.'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if some opcode cache is loaded
|
||||
*/
|
||||
protected function checkSomePhpOpcodeCacheIsLoaded()
|
||||
{
|
||||
$opcodeCaches = GeneralUtility::makeInstance(OpcodeCacheService::class)->getAllActive();
|
||||
if (empty($opcodeCaches)) {
|
||||
// Set status to notice. It needs to be notice so email won't be triggered.
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'PHP opcode caches hold a compiled version of executed PHP scripts in'
|
||||
. ' memory and do not require to recompile a script each time it is accessed.'
|
||||
. ' This can be a massive performance improvement and can reduce the load on a'
|
||||
. ' server in general. A parse time reduction by factor three for fully cached'
|
||||
. ' pages can be achieved easily if using an opcode cache.',
|
||||
'No PHP opcode cache loaded',
|
||||
ContextualFeedbackSeverity::NOTICE
|
||||
));
|
||||
} else {
|
||||
$status = ContextualFeedbackSeverity::OK;
|
||||
$message = '';
|
||||
foreach ($opcodeCaches as $opcodeCache => $properties) {
|
||||
$message .= 'Name: ' . $opcodeCache . ' Version: ' . $properties['version'];
|
||||
$message .= LF;
|
||||
if ($properties['warning']) {
|
||||
$status = ContextualFeedbackSeverity::WARNING;
|
||||
$message .= ' ' . $properties['warning'];
|
||||
} else {
|
||||
$message .= ' This opcode cache should work correctly and has good performance.';
|
||||
}
|
||||
$message .= LF;
|
||||
}
|
||||
// Set title of status depending on severity
|
||||
switch ($status) {
|
||||
case ContextualFeedbackSeverity::WARNING:
|
||||
$title = 'A possibly malfunctioning PHP opcode cache is loaded';
|
||||
break;
|
||||
case ContextualFeedbackSeverity::OK:
|
||||
default:
|
||||
$title = 'A PHP opcode cache is loaded';
|
||||
break;
|
||||
}
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
$message,
|
||||
$title,
|
||||
$status
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create true type font test image
|
||||
*/
|
||||
protected function isTrueTypeFontWorking()
|
||||
{
|
||||
if (function_exists('imageftbbox')) {
|
||||
// 20 Pixels at 96 DPI
|
||||
$fontSize = (20 / 96 * 72);
|
||||
$textDimensions = @imageftbbox(
|
||||
$fontSize,
|
||||
0,
|
||||
__DIR__ . '/../../Resources/Private/Font/vera.ttf',
|
||||
'Testing true type support'
|
||||
);
|
||||
$fontBoxWidth = $textDimensions[2] - $textDimensions[0];
|
||||
if ($fontBoxWidth < 300 && $fontBoxWidth > 200) {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Fonts are rendered by FreeType library. '
|
||||
. 'We need to ensure that the final dimensions are as expected. '
|
||||
. 'This server renderes fonts based on 96 DPI correctly',
|
||||
'FreeType True Type Font DPI'
|
||||
));
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'Fonts are rendered by FreeType library. '
|
||||
. 'This server does not render fonts as expected. '
|
||||
. 'Please check your FreeType 2 module.',
|
||||
'FreeType True Type Font DPI',
|
||||
ContextualFeedbackSeverity::NOTICE
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$this->messageQueue->enqueue(new FlashMessage(
|
||||
'The core relies on GD library compiled into PHP with freetype2'
|
||||
. ' support. This is missing on your system. Please install it.',
|
||||
'PHP GD library freetype2 support missing',
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user