Commit ad43bc30 authored by Manuel Selbach's avatar Manuel Selbach Committed by Andreas Fernandez
Browse files

[TASK] Separate database requirements checks in Install tool

This change separates database requirement checks regarding the DBMS
platform (e.g. MySQL, PostgreSQL, ...) from drivers as checks for
specific drivers may differ (e.g. pdo_mysql requires other PHP
extensions being loaded than mysqli).

Additionally the encoding for the database is defined in
\TYPO3\CMS\Core\Database\Platform\PlatformInformation
which will be enforced during the install process of the Install tool.

Resolves: #89794
Releases: master
Change-Id: I45ff0c57c3af30af0aff349520a0dbea3937eccd
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62472


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 5c453acb
......@@ -283,9 +283,10 @@ class Installer {
this.checkDatabaseSelect();
} else {
if (Array.isArray(data.status)) {
$outputContainer.empty();
data.status.forEach((element: any): void => {
let message: any = InfoBox.render(element.severity, element.title, element.message);
$outputContainer.empty().append(message);
$outputContainer.append(message);
});
}
}
......@@ -332,7 +333,7 @@ class Installer {
.then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.checkDatabaseData();
this.checkDatabaseRequirements();
} else {
if (Array.isArray(data.status)) {
data.status.forEach((element: any): void => {
......@@ -344,6 +345,35 @@ class Installer {
});
}
private checkDatabaseRequirements(): void {
let $outputContainer: JQuery = $(this.selectorDatabaseSelectOutput);
let postData: any = {
'install[action]': 'checkDatabaseRequirements',
'install[token]': $(this.selectorModuleContent).data('installer-database-check-requirements-execute-token'),
};
$($(this.selectorBody + ' form').serializeArray()).each((index: number, element: any): void => {
postData[element.name] = element.value;
});
(new AjaxRequest(this.getUrl()))
.post(postData)
.then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.checkDatabaseData();
} else {
if (Array.isArray(data.status)) {
$outputContainer.empty();
data.status.forEach((element: any): void => {
let message: any = InfoBox.render(element.severity, element.title, element.message);
$outputContainer.append(message);
});
}
}
});
}
private checkDatabaseData(): void {
this.setProgress(4);
(new AjaxRequest(this.getUrl('checkDatabaseData')))
......
......@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Database\Platform;
* The TYPO3 project - inspiring people to share!
*/
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
......@@ -49,6 +50,54 @@ class PlatformInformation
'sqlite' => 999,
];
protected static $charSetMap = [
'mysql' => 'utf8mb4',
'postgresql' => 'UTF8',
'sqlserver' => 'UTF-8',
'sqlite' => 'utf8',
];
protected static $databaseCreateWithCharsetMap = [
'mysql' => 'CHARACTER SET %s',
'postgresql' => "ENCODING '%s'",
'sqlserver' => '',
];
/**
* Return the encoding of the given platform
*
* @param AbstractPlatform $platform
* @return string
*/
public static function getCharset(AbstractPlatform $platform): string
{
$platformName = static::getPlatformIdentifier($platform);
return static::$charSetMap[$platformName];
}
/**
* Return the statement to create a database with the desired encoding for the given platform
*
* @param AbstractPlatform $platform
* @param string $databaseName
* @return string
*/
public static function getDatabaseCreateStatementWithCharset(AbstractPlatform $platform, string $databaseName): string
{
try {
$createStatement = $platform->getCreateDatabaseSQL($databaseName);
} catch (DBALException $exception) {
// just silently ignore that error as the selected database does not support any creation of a database
return '';
}
$platformName = static::getPlatformIdentifier($platform);
$charset = static::getCharset($platform);
return $createStatement . ' ' . sprintf(static::$databaseCreateWithCharsetMap[$platformName], $charset);
}
/**
* Return information about the maximum supported length for a SQL identifier.
*
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
......@@ -63,7 +65,6 @@ class Check implements CheckInterface
'gd',
'hash',
'json',
'mysqli',
'session',
'SPL',
'standard',
......@@ -81,6 +82,16 @@ class Check implements CheckInterface
'openssl' => 'This extension is used for sending SMTP mails over an encrypted channel endpoint, and for extensions such as "rsaauth".'
];
public function __construct()
{
$this->messageQueue = new FlashMessageQueue('install');
}
public function getMessageQueue(): FlashMessageQueue
{
return $this->messageQueue;
}
/**
* Get all status information as array with status objects
*
......@@ -88,7 +99,6 @@ class Check implements CheckInterface
*/
public function getStatus(): FlashMessageQueue
{
$this->messageQueue = new FlashMessageQueue('install');
$this->checkCurrentDirectoryIsInIncludePath();
$this->checkFileUploadEnabled();
$this->checkPostUploadSizeIsHigherOrEqualMaximumFileUploadSize();
......@@ -96,7 +106,6 @@ class Check implements CheckInterface
$this->checkPhpVersion();
$this->checkMaxExecutionTime();
$this->checkDisableFunctions();
$this->checkMysqliReconnectSetting();
$this->checkDocRoot();
$this->checkOpenBaseDir();
$this->checkXdebugMaxNestingLevel();
......@@ -400,29 +409,6 @@ class Check implements CheckInterface
}
}
/**
* 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
*/
......@@ -623,7 +609,7 @@ class Check implements CheckInterface
* @param bool $required
* @param string $purpose
*/
protected function checkPhpExtension(string $extension, bool $required = true, string $purpose = '')
public function checkPhpExtension(string $extension, bool $required = true, string $purpose = '')
{
if (!extension_loaded($extension)) {
$this->messageQueue->enqueue(new FlashMessage(
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Install\SystemEnvironment;
/*
......@@ -13,9 +15,32 @@ namespace TYPO3\CMS\Install\SystemEnvironment;
*
* The TYPO3 project - inspiring people to share!
*/
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver as DoctrineDrizzlePDOMySQLDriver;
use Doctrine\DBAL\Driver\IBMDB2\DB2Driver;
use Doctrine\DBAL\Driver\Mysqli\Driver as DoctrineMysqliDriver;
use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineOCI8Driver;
use Doctrine\DBAL\Driver\PDOOracle\Driver as DoctrinePDOOCIDriver;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver as DoctrineSQLAnywhereDriver;
use Doctrine\DBAL\Driver\SQLSrv\Driver as DoctrineSQLSrvDriver;
use TYPO3\CMS\Core\Database\Driver\PDOMySql\Driver as TYPO3PDOMySqlDriver;
use TYPO3\CMS\Core\Database\Driver\PDOPgSql\Driver as TYPO3PDOPgSqlDriver;
use TYPO3\CMS\Core\Database\Driver\PDOSqlite\Driver as TYPO3PDOSqliteDriver;
use TYPO3\CMS\Core\Database\Driver\PDOSqlsrv\Driver as TYPO3PDOSqlSrvDriver;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform\MySqlCheck;
use TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform\PostgreSqlCheck;
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\Driver\PDOSqlsrv as DatabaseCheckDriverPDOSqlsrv;
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver\SQLSrv as DatabaseCheckDriverSQLSrv;
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;
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform\SqlSrv as DatabaseCheckPlatformSqlSrv;
/**
* Check database configuration status
......@@ -26,34 +51,279 @@ use TYPO3\CMS\Install\SystemEnvironment\DatabasePlatform\PostgreSqlCheck;
* 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 array
* @var string[]
*/
protected $databasePlatformChecks = [
MySqlCheck::class,
PostgreSqlCheck::class,
private static $databaseDriverToPlatformMapping = [
DoctrineMysqliDriver::class => DatabaseCheckPlatformMysql::class,
TYPO3PDOMySqlDriver::class => DatabaseCheckPlatformMysql::class,
TYPO3PDOPgSqlDriver::class => DatabaseCheckPlatformPostgreSql::class,
DoctrineSQLSrvDriver::class => DatabaseCheckPlatformSqlSrv::class,
TYPO3PDOSqlSrvDriver::class => DatabaseCheckPlatformSqlSrv::class,
TYPO3PDOSqliteDriver::class => DatabaseCheckPlatformSqlite::class,
];
private static $driverMap = [
'pdo_mysql' => TYPO3PDOMySqlDriver::class,
'pdo_sqlite' => TYPO3PDOSqliteDriver::class,
'pdo_pgsql' => TYPO3PDOPgSqlDriver::class,
'pdo_oci' => DoctrinePDOOCIDriver::class,
'oci8' => DoctrineOCI8Driver::class,
'ibm_db2' => DB2Driver::class,
'pdo_sqlsrv' => TYPO3PDOSqlSrvDriver::class,
'mysqli' => DoctrineMysqliDriver::class,
'drizzle_pdo_mysql' => DoctrineDrizzlePDOMySQLDriver::class,
'sqlanywhere' => DoctrineSQLAnywhereDriver::class,
'sqlsrv' => DoctrineSQLSrvDriver::class,
];
/**
* Get status of each database platform defined in the list
* @var CheckInterface[]
*/
private $databaseDriverCheckMap = [
DoctrineMysqliDriver::class => DatabaseCheckDriverMysqli::class,
TYPO3PDOMySqlDriver::class => DatabaseCheckDriverPdoMysql::class,
TYPO3PDOPgSqlDriver::class => DatabaseCheckDriverPDOPgSql::class,
DoctrineSQLSrvDriver::class => DatabaseCheckDriverSQLSrv::class,
TYPO3PDOSqlSrvDriver::class => DatabaseCheckDriverPDOSqlsrv::class,
TYPO3PDOSqliteDriver::class => DatabaseCheckDriverPDOSqlite::class,
];
public function __construct()
{
$this->messageQueue = new FlashMessageQueue('install-database-check');
}
/**
* Get status of each database platform identified to be installed on the system
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue
{
$messageQueue = new FlashMessageQueue('install');
foreach ($this->databasePlatformChecks as $databasePlatformCheckClass) {
$platformMessageQueue = (new $databasePlatformCheckClass)->getStatus();
$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(),
FlashMessage::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(),
FlashMessage::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
*
* @param string $databaseDriver
* @return FlashMessageQueue
*
* @throws Exception
*/
public function checkDatabasePlatformRequirements(string $databaseDriver): FlashMessageQueue
{
static $checkedPlatform = [];
$databasePlatformClass = static::$databaseDriverToPlatformMapping[$databaseDriver];
// execute platform checks only once
if (in_array($databasePlatformClass, $checkedPlatform, true)) {
return $this->messageQueue;
}
if (!empty(static::$databaseDriverToPlatformMapping[$databaseDriver])) {
$platformMessageQueue = (new $databasePlatformClass)->getStatus();
foreach ($platformMessageQueue as $message) {
$messageQueue->enqueue($message);
$this->messageQueue->enqueue($message);
}
$checkedPlatform[] = $databasePlatformClass;
return $this->messageQueue;
}
return $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[] = TYPO3PDOMySqlDriver::class;
}
if (static::isPdoPgsql()) {
$installedDrivers[] = TYPO3PDOPgSqlDriver::class;
}
if (static::isPdoSqlite()) {
$installedDrivers[] = TYPO3PDOSqliteDriver::class;
}
if (static::isPdoSqlSrv()) {
$installedDrivers[] = TYPO3PDOSqlSrvDriver::class;
}
if (static::isSqlSrv()) {
$installedDrivers[] = Driver\SQLSrv\Driver::class;
}
return $installedDrivers;
}
/**
* @param string $databaseDriverName
* @return string
*
* @throws Exception
*/
public static function retrieveDatabasePlatformByDriverName(string $databaseDriverName): string
{
$databaseDriverClassName = static::retrieveDatabaseDriverClassByDriverName($databaseDriverName);
if (!empty(static::$databaseDriverToPlatformMapping[$databaseDriverClassName])) {
return static::$databaseDriverToPlatformMapping[$databaseDriverClassName];
}
throw new Exception(
sprintf('There is no database platform available for the given driver: %s', $databaseDriverName),
1573753057
);
}
/**
* @param string $driverName
* @return Driver
* @throws Exception
*/
public static function retrieveDatabaseDriverClassByDriverName(string $driverName): string
{
if (!empty(static::$driverMap[$driverName])) {
return static::$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');
}
public static function isPdoSqlSrv(): bool
{
return extension_loaded('pdo_sqlsrv');
}
public static function isSqlSrv(): bool
{
return extension_loaded('sqlsrv');
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Driver;
/*
* 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\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
*
* @return FlashMessageQueue
*/
public function getStatus(): FlashMessageQueue
{
return $this->messageQueue;
}