first commit

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

No files matched your search

@@ -0,0 +1,859 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
/**
* 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. It should not fail
* if there is no TYPO3 at all.
*
* The only core code used is the class loader
*
* This class is instantiated as the *very first* class during
* installation. It is meant to be *standalone* und must not have
* any requirements, except the status classes. It must be possible
* to run this script separated from the rest of the core, without
* dependencies.
*
* This means especially:
* * No hooks or anything like that
* * No usage of *any* TYPO3 code like GeneralUtility
* * No require of anything but the status classes
* * No localization
*
* 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 Check implements CheckInterface
{
/**
* @var FlashMessageQueue
*/
protected $messageQueue;
/**
* @var array List of required PHP extensions
*/
protected $requiredPhpExtensions = [
'filter',
'gd',
'hash',
'json',
'mysqli',
'session',
'SPL',
'standard',
'xml',
'zip',
'zlib',
];
/**
* @var string[]
*/
protected $suggestedPhpExtensions = [
'fileinfo' => 'This extension is used for proper file type detection in the File Abstraction Layer.',
'intl' => 'This extension is used for correct language and locale handling.',
'openssl' => 'This extension is used for sending SMTP mails over an encrypted channel endpoint, and for extensions such as "rsaauth".'
];
/**
* Get all status information as array with status objects
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue
{
$this->messageQueue = new FlashMessageQueue('install');
$this->checkCurrentDirectoryIsInIncludePath();
$this->checkFileUploadEnabled();
$this->checkPostUploadSizeIsHigherOrEqualMaximumFileUploadSize();
$this->checkMemorySettings();
$this->checkPhpVersion();
$this->checkMaxExecutionTime();
$this->checkDisableFunctions();
$this->checkMysqliReconnectSetting();
$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 = ini_get('include_path');
$delimiter = $this->isWindowsOs() ? ';' : ':';
$pathArray = $this->trimExplode($delimiter, $includePath);
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',
FlashMessage::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',
FlashMessage::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(ini_get('upload_max_filesize'));
$maximumPostSize = $this->getBytesFromSizeMeasurement(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',
FlashMessage::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 = 64;
$recommendedMemoryLimit = 128;
$memoryLimit = $this->getBytesFromSizeMeasurement(ini_get('memory_limit'));
if ($memoryLimit <= 0) {
$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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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 = '7.2.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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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) {
$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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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(ini_get('disable_functions'));
// Filter "disable_functions"
$disabledFunctionsArray = $this->trimExplode(',', $disabledFunctions);
// Array with strings to find
$findStrings = [
// Disabled by default on Ubuntu OS but this is okay since the Core does not use them
'pcntl_',
];
foreach ($disabledFunctionsArray as $key => $disabledFunction) {
foreach ($findStrings as $findString) {
if (strpos($disabledFunction, $findString) !== false) {
unset($disabledFunctionsArray[$key]);
}
}
}
if ($disabledFunctions !== '') {
if (!empty($disabledFunctionsArray)) {
$this->messageQueue->enqueue(new FlashMessage(
'disable_functions=' . implode(' ', explode(',', $disabledFunctions)) . 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 most likely 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.',
'Some PHP functions disabled',
FlashMessage::ERROR
));
} else {
$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'
));
}
}
/**
* Verify that mysqli.reconnect is set to 0 in order to avoid improper reconnects
*/
protected function checkMysqliReconnectSetting()
{
$currentMysqliReconnectSetting = ini_get('mysqli.reconnect');
if ($currentMysqliReconnectSetting === '1') {
$this->messageQueue->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',
FlashMessage::ERROR
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'PHP mysqli.reconnect is fine'
));
}
}
/**
* Check for doc_root ini setting
*/
protected function checkDocRoot()
{
$docRootSetting = trim(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',
FlashMessage::NOTICE
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'PHP doc_root is not set'
));
}
}
/**
* Check open_basedir
*/
protected function checkOpenBaseDir()
{
$openBaseDirSetting = trim(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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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 ' . TYPO3_URL_WIKI_OPCODECACHE . '.',
'PHP Doc comment reflection broken',
FlashMessage::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 ($this->isWindowsOs()
&& strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === 0
) {
$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',
FlashMessage::WARNING
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'Apache ThreadStackSize is not an issue on UNIX systems'
));
}
}
/**
* Checks if a specific PHP extension is loaded.
*
* @param string $extension
* @param bool $required
* @param string $purpose
*/
protected 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 ? FlashMessage::ERROR : FlashMessage::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 (is_resource($imageResource)) {
imagedestroy($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',
FlashMessage::ERROR
));
}
} else {
$this->messageQueue->enqueue(new FlashMessage(
'Gdlib is essential for TYPO3 CMS to work properly.',
'PHP GD library true color support missing',
FlashMessage::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 (is_resource($imageResource)) {
imagedestroy($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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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 (is_resource($imageResource)) {
imagedestroy($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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::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 (!stristr(PHP_OS, 'darwin') && stristr(PHP_OS, 'win')) {
$windowsOs = true;
}
return $windowsOs;
}
/**
* Helper method to explode a string by delimiter and throw away empty values.
* Removes empty values from result array.
*
* @param string $delimiter Delimiter string to explode with
* @param string $string The string to explode
* @return array Exploded values
*/
protected function trimExplode($delimiter, $string)
{
$explodedValues = explode($delimiter, $string);
$resultWithPossibleEmptyValues = array_map('trim', $explodedValues);
$result = [];
foreach ($resultWithPossibleEmptyValues as $value) {
if ($value !== '') {
$result[] = $value;
}
}
return $result;
}
/**
* 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;
}
}
@@ -0,0 +1,38 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\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
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue;
}
@@ -0,0 +1,59 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform\MySqlCheck;
use TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform\PostgreSqlCheck;
/**
* 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).
*
* @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
{
/**
* List of database platforms to check
*
* @var array
*/
protected $databasePlatformChecks = [
MySqlCheck::class,
PostgreSqlCheck::class,
];
/**
* Get status of each database platform defined in the list
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue
{
$messageQueue = new FlashMessageQueue('install');
foreach ($this->databasePlatformChecks as $databasePlatformCheckClass) {
$platformMessageQueue = (new $databasePlatformCheckClass)->getStatus();
foreach ($platformMessageQueue as $message) {
$messageQueue->enqueue($message);
}
}
return $messageQueue;
}
}
@@ -0,0 +1,177 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
/**
* 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 MySqlCheck implements CheckInterface
{
/**
* @var FlashMessageQueue
*/
protected $messageQueue;
/**
* Minimum supported MySQL version
*
* @var string
*/
protected $minimumMySQLVersion = '5.5.0';
/**
* List of MySQL modes that are incompatible with TYPO3 CMS
*
* @var array
*/
protected $incompatibleSqlModes = [
'NO_BACKSLASH_ESCAPES'
];
/**
* Get all status information as array with status objects
*
* @return FlashMessageQueue
* @throws \InvalidArgumentException
* @throws \Doctrine\DBAL\DBALException
*/
public function getStatus(): FlashMessageQueue
{
$this->messageQueue = new FlashMessageQueue('install');
$defaultConnection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
if (strpos($defaultConnection->getServerVersion(), 'MySQL') !== 0) {
return $this->messageQueue;
}
$this->checkMysqlVersion($defaultConnection);
$this->checkInvalidSqlModes($defaultConnection);
$this->checkMysqlDatabaseUtf8Status($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!',
FlashMessage::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 checkMysqlVersion(Connection $connection)
{
preg_match('/MySQL ((\d+\.)*(\d+\.)*\d+)/', $connection->getServerVersion(), $match);
$currentMysqlVersion = $match[1];
if (version_compare($currentMysqlVersion, $this->minimumMySQLVersion, '<')) {
$this->messageQueue->enqueue(new FlashMessage(
'Your MySQL version ' . $currentMysqlVersion . ' is too old. TYPO3 CMS does not run'
. ' with this version. Update to at least MySQL ' . $this->minimumMySQLVersion,
'MySQL version too low',
FlashMessage::ERROR
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'MySQL version 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
*/
protected function checkMysqlDatabaseUtf8Status(Connection $connection)
{
$queryBuilder = $connection->createQueryBuilder();
$defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
->from('information_schema.SCHEMATA')
->where(
$queryBuilder->expr()->eq(
'SCHEMA_NAME',
$queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
)
)
->setMaxResults(1)
->execute()
->fetchColumn();
// also allow utf8mb4
if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
$this->messageQueue->enqueue(new FlashMessage(
'Checking database character set failed, got key "'
. $defaultDatabaseCharset . '" instead of "utf8" or "utf8mb4"',
'MySQL database character set check failed',
FlashMessage::ERROR
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'Your database uses utf-8. All good.'
));
}
}
/**
* 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(',', $connection->executeQuery('SELECT @@SESSION.sql_mode;')
->fetch(0)['@@SESSION.sql_mode']);
return array_intersect($this->incompatibleSqlModes, $sqlModes);
}
}
@@ -0,0 +1,136 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
/**
* 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 PostgreSqlCheck implements CheckInterface
{
/**
* @var FlashMessageQueue
*/
protected $messageQueue;
/**
* Minimum supported PostgreSQL Server version
*
* @var string
*/
protected $minimumPostgreSQLVerion = '9.2';
/**
* Minimum supported libpq version
* @var string
*/
protected $minimumLibPQVersion = '9.0';
/**
* Get all status information as array with status objects
*
* @return FlashMessageQueue
* @throws \Doctrine\DBAL\DBALException
* @throws \InvalidArgumentException
*/
public function getStatus(): FlashMessageQueue
{
$this->messageQueue = new FlashMessageQueue('install');
$defaultConnection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
if (strpos($defaultConnection->getServerVersion(), 'PostgreSQL') !== 0) {
return $this->messageQueue;
}
$this->checkPostgreSqlVersion($defaultConnection);
$this->checkLibpqVersion();
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->getServerVersion(), $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',
FlashMessage::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_STR')) {
$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',
FlashMessage::WARNING
));
} else {
preg_match('/PostgreSQL ((\d+\.)*(\d+\.)*\d+)/', \PGSQL_LIBPQ_VERSION_STR, $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',
FlashMessage::ERROR
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'PostgreSQL libpq version is supported'
));
}
}
}
}
@@ -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,75 @@
<?php
declare(strict_types = 1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\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(): bool
{
$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'])
: 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,272 @@
<?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;
/**
* 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
*/
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) === [];
}
/**
* @param ResponseInterface $response
* @return StatusMessage[]
*/
public function getMismatches(ResponseInterface $response): array
{
$mismatches = [];
if ($this->handler instanceof \Closure) {
$result = $this->handler->call($this, $response);
if ($result !== null) {
$mismatches[] = $result;
}
return $mismatches;
}
$body = (string)$response->getBody();
$contentType = $response->getHeaderLine('content-type');
if ($this->expectedContent !== null && strpos($body, $this->expectedContent) === false) {
$mismatches[] = new StatusMessage(
'content mismatch %s',
$this->expectedContent,
$body
);
}
if ($this->unexpectedContent !== null && strpos($body, $this->unexpectedContent) !== false) {
$mismatches[] = new StatusMessage(
'unexpected content %s',
$this->unexpectedContent,
$body
);
}
if ($this->expectedContentType !== null
&& strpos($contentType . ';', $this->expectedContentType . ';') !== 0) {
$mismatches[] = new StatusMessage(
'content-type mismatch %s, got %s',
$this->expectedContentType,
$contentType
);
}
if ($this->unexpectedContentType !== null
&& strpos($contentType . ';', $this->unexpectedContentType . ';') === 0) {
$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;
}
/**
* @return FileLocation
*/
public function getFileLocation(): FileLocation
{
return $this->fileLocation;
}
/**
* @return string
*/
public function getFileName(): string
{
return $this->fileName;
}
/**
* @return string
*/
public function getUrl(): string
{
return $this->fileLocation->getBaseUrl() . $this->fileName;
}
/**
* @return bool
*/
public function shallFail(): bool
{
return $this->fail;
}
/**
* @return string|null
*/
public function getExpectedContentType(): ?string
{
return $this->expectedContentType;
}
/**
* @return string|null
*/
public function getUnexpectedContentType(): ?string
{
return $this->unexpectedContentType;
}
/**
* @return string|null
*/
public function getExpectedContent(): ?string
{
return $this->expectedContent;
}
/**
* @return string|null
*/
public function getUnexpectedContent(): ?string
{
return $this->unexpectedContent;
}
}
@@ -0,0 +1,69 @@
<?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 TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
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
{
/**
* @var string
*/
protected $path;
/**
* @var string
*/
protected $filePath;
/**
* @var string
*/
protected $baseUrl;
public function __construct(string $path)
{
$this->path = $path;
$this->filePath = Environment::getPublicPath() . $path;
$this->baseUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST')
. PathUtility::getAbsoluteWebPath($this->filePath);
}
/**
* @return string
*/
public function getFilePath(): string
{
return $this->filePath;
}
/**
* @return string
*/
public function getBaseUrl(): string
{
return $this->baseUrl;
}
}
@@ -0,0 +1,323 @@
<?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 function GuzzleHttp\Promise\settle;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
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 bool
*/
protected $useMarkup;
/**
* @var FlashMessageQueue
*/
protected $messageQueue;
/**
* @var FileLocation
*/
protected $assetLocation;
/**
* @var FileLocation
*/
protected $fileadminLocation;
/**
* @var FileDeclaration[]
*/
protected $fileDeclarations;
public function __construct(bool $useMarkup = true)
{
$this->useMarkup = $useMarkup;
$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(): Status
{
$messageQueue = $this->getStatus();
$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/master/en-us/Changelog/9.5.x/Feature-91354-IntegrateServerResponseSecurityChecks.html',
'Please see documentation for further details...'
);
if ($messageQueue->getAllMessages(FlashMessage::ERROR) !== []) {
$title = 'Potential vulnerabilities';
$label = $detailsLink;
$severity = Status::ERROR;
} elseif ($messageQueue->getAllMessages(FlashMessage::WARNING) !== []) {
$title = 'Warnings';
$label = $detailsLink;
$severity = Status::WARNING;
}
return new Status(
'Server Response on static files',
$title ?? 'OK',
$this->wrapList($messages, $label ?? '', self::WRAP_NESTED),
$severity ?? Status::OK
);
}
public function getStatus(): FlashMessageQueue
{
$messageQueue = new FlashMessageQueue('install-server-response-check');
if (PHP_SAPI === 'cli-server') {
$messageQueue->addMessage(
new FlashMessage(
'Skipped for PHP_SAPI=cli-server',
'Checks skipped',
FlashMessage::WARNING
)
);
return $messageQueue;
}
try {
$this->buildFileDeclarations();
$this->processFileDeclarations($messageQueue);
$this->finishMessageQueue($messageQueue);
} finally {
$this->purgeFileDeclarations();
}
return $messageQueue;
}
protected function initializeFileDeclarations(string $fileName): array
{
$cspClosure = function (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()) {
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);
}
file_put_contents(
$filePath . $fileDeclaration->getFileName(),
$fileDeclaration->buildContent()
);
}
}
protected function purgeFileDeclarations(): void
{
GeneralUtility::rmdir($this->assetLocation->getFilePath(), true);
GeneralUtility::rmdir($this->fileadminLocation->getFilePath(), true);
}
protected function processFileDeclarations(FlashMessageQueue $messageQueue): void
{
$promises = [];
$client = new Client(['timeout' => 10]);
foreach ($this->fileDeclarations as $fileDeclaration) {
$promises[] = $client->requestAsync('GET', $fileDeclaration->getUrl());
}
foreach (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',
FlashMessage::WARNING
)
);
continue;
}
if (!($response['value'] ?? null) instanceof ResponseInterface || $fileDeclaration->matches($response['value'])) {
continue;
}
$messageQueue->addMessage(
new FlashMessage(
$this->createMismatchMessage($fileDeclaration, $response['value']),
'Unexpected server response',
$fileDeclaration->shallFail() ? FlashMessage::ERROR : FlashMessage::WARNING
)
);
}
}
protected function finishMessageQueue(FlashMessageQueue $messageQueue): void
{
if ($messageQueue->getAllMessages(FlashMessage::WARNING) !== []
|| $messageQueue->getAllMessages(FlashMessage::ERROR) !== []) {
return;
}
$messageQueue->addMessage(
new FlashMessage(
sprintf('All %d files processed correctly', count($this->fileDeclarations)),
'Expected server response',
FlashMessage::OK
)
);
}
protected function createMismatchMessage(FileDeclaration $fileDeclaration, ResponseInterface $response): 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(), 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,56 @@
<?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 $message;
/**
* @var string[]
*/
protected $values;
public function __construct(string $message, string ...$values)
{
$this->message = $message;
$this->values = $values;
}
/**
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* @return string[]
*/
public function getValues(): array
{
return $this->values;
}
}
@@ -0,0 +1,312 @@
<?php
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Service\OpcodeCacheService;
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
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue
{
$this->messageQueue = new FlashMessageQueue('install');
$this->checkTrustedHostPattern();
$this->checkDownloadsPossible();
$this->checkSystemLocale();
$this->checkLocaleWithUTF8filesystem();
$this->checkSomePhpOpcodeCacheIsLoaded();
$this->isTrueTypeFontWorking();
$this->checkLibXmlBug();
return $this->messageQueue;
}
/**
* Checks the status of the trusted hosts pattern check
*/
protected function checkTrustedHostPattern()
{
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::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',
FlashMessage::WARNING
));
} else {
if (GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
$this->messageQueue->enqueue(new FlashMessage(
'',
'Trusted hosts pattern is configured to allow current host value.'
));
} else {
$defaultPort = GeneralUtility::getIndpEnv('TYPO3_SSL') ? '443' : '80';
$this->messageQueue->enqueue(new FlashMessage(
'The trusted hosts pattern will be configured to allow all header values. This is because your $SERVER_NAME:[defaultPort]'
. ' is "' . $_SERVER['SERVER_NAME'] . ':' . $defaultPort . '" while your HTTP_HOST:SERVER_PORT is "'
. $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . '". 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 mismatch',
FlashMessage::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',
FlashMessage::WARNING
));
}
}
/**
* Check if systemLocale setting is correct (locale exists in the OS)
*/
protected function checkSystemLocale()
{
$currentLocale = 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',
FlashMessage::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',
FlashMessage::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',
FlashMessage::ERROR
));
} else {
$testString = 'ÖöĄĆŻĘĆćążąęó.jpg';
$currentLocale = 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.',
FlashMessage::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()
{
// Link to our documentation page, so we can update opcode cache issue information independent of TYPO3 CMS releases.
$documentationLink = 'For more information take a look in our documentation ' . TYPO3_URL_WIKI_OPCODECACHE . '.';
$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.'
. LF . $documentationLink,
'No PHP opcode cache loaded',
FlashMessage::NOTICE
));
} else {
$status = FlashMessage::OK;
$message = '';
foreach ($opcodeCaches as $opcodeCache => $properties) {
$message .= 'Name: ' . $opcodeCache . ' Version: ' . $properties['version'];
$message .= LF;
if ($properties['error']) {
$status = FlashMessage::ERROR;
$message .= ' This opcode cache is marked as malfunctioning by the TYPO3 CMS Team.';
} elseif ($properties['canInvalidate']) {
$message .= ' This opcode cache should work correctly and has good performance.';
} else {
// Set status to notice if not already error set. It needs to be notice so email won't be triggered.
if ($status !== FlashMessage::ERROR) {
$status = FlashMessage::NOTICE;
}
$message .= ' This opcode cache may work correctly but has medium performance.';
}
$message .= LF;
}
$message .= $documentationLink;
// Set title of status depending on serverity
switch ($status) {
case FlashMessage::ERROR:
$title = 'A possibly malfunctioning PHP opcode cache is loaded';
break;
case FlashMessage::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',
FlashMessage::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',
FlashMessage::ERROR
));
}
}
/**
* Check for bug in libxml
*/
protected function checkLibXmlBug()
{
$sampleArray = ['Test>><<Data'];
$xmlContent = '<numIndex index="0">Test&gt;&gt;&lt;&lt;Data</numIndex>' . LF;
$xml = GeneralUtility::array2xml($sampleArray, '', -1);
if ($xmlContent !== $xml) {
$this->messageQueue->enqueue(new FlashMessage(
'Some hosts have problems saving ">><<" in a flexform.'
. ' To fix this, enable [BE][flexformForceCDATA] in'
. ' All Configuration.',
'PHP libxml bug present',
FlashMessage::ERROR
));
} else {
$this->messageQueue->enqueue(new FlashMessage(
'',
'PHP libxml bug not present'
));
}
}
}