From 2c3d6f40bb68ecc81a82fb7e8aa74750d5899880 Mon Sep 17 00:00:00 2001 From: tobiasadolph <mail@tobiasadolph.de> Date: Tue, 9 Aug 2016 18:22:00 +0200 Subject: [PATCH] [TASK] Doctrine: Migrate DatabaseSelect-Step Resolves: #77448 Releases: master Change-Id: Idb5be03b0ac996646fffc0bcd75c439f2a19b05f Reviewed-on: https://review.typo3.org/49435 Tested-by: Bamboo TYPO3com <info@typo3.com> Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de> Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> --- .../Controller/Action/Step/DatabaseSelect.php | 309 ++++++++++-------- 1 file changed, 178 insertions(+), 131 deletions(-) diff --git a/typo3/sysext/install/Classes/Controller/Action/Step/DatabaseSelect.php b/typo3/sysext/install/Classes/Controller/Action/Step/DatabaseSelect.php index ba19393f19b8..25cf4a618a22 100644 --- a/typo3/sysext/install/Classes/Controller/Action/Step/DatabaseSelect.php +++ b/typo3/sysext/install/Classes/Controller/Action/Step/DatabaseSelect.php @@ -14,7 +14,13 @@ namespace TYPO3\CMS\Install\Controller\Action\Step; * The TYPO3 project - inspiring people to share! */ +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\DriverManager; +use TYPO3\CMS\Core\Configuration\ConfigurationManager; +use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Status\ErrorStatus; +use TYPO3\CMS\Install\Status\OkStatus; /** * Database select step. @@ -31,100 +37,28 @@ class DatabaseSelect extends AbstractStepAction /** * Create database if needed, save selected db name in configuration * - * @return array<\TYPO3\CMS\Install\Status\StatusInterface> + * @return \TYPO3\CMS\Install\Status\StatusInterface[] */ public function execute() { - $result = array(); - $this->initializeDatabaseConnection(); $postValues = $this->postValues['values']; - $localConfigurationPathValuePairs = array(); - /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */ - $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class); - $canProceed = true; if ($postValues['type'] === 'new') { - $newDatabaseName = $postValues['new']; - if ($this->isValidDatabaseName($newDatabaseName)) { - $createDatabaseResult = $this->databaseConnection->admin_query('CREATE DATABASE ' . $newDatabaseName . ' CHARACTER SET utf8'); - if ($createDatabaseResult) { - $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $newDatabaseName; - } else { - /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */ - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); - $errorStatus->setTitle('Unable to create database'); - $errorStatus->setMessage( - 'Database with name ' . $newDatabaseName . ' could not be created.' . - ' Either your database name contains a reserved keyword or your database' . - ' user does not have sufficient permissions to create it.' . - ' Please choose an existing (empty) database or contact administration.' - ); - $result[] = $errorStatus; - } - } else { - /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */ - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); - $errorStatus->setTitle('Database name not valid'); - $errorStatus->setMessage( - 'Given database name must be shorter than fifty characters' . - ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)' . - ' and underscores (_).' - ); - $result[] = $errorStatus; + $status = $this->createNewDatabase($postValues['new']); + if ($status instanceof ErrorStatus) { + return [ $status ]; } } elseif ($postValues['type'] === 'existing' && !empty($postValues['existing'])) { - // Only store database information when it's empty - $this->databaseConnection->setDatabaseName($postValues['existing']); - try { - $this->databaseConnection->sql_select_db(); - $existingTables = $this->databaseConnection->admin_get_tables(); - if (!empty($existingTables)) { - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); - $errorStatus->setTitle('Selected database is not empty!'); - $errorStatus->setMessage( - sprintf('Cannot use database "%s"', $postValues['existing']) - . ', because it has tables already. Please select a different database or choose to create one!' - ); - $result[] = $errorStatus; - } - } catch (\RuntimeException $e) { - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); - $errorStatus->setTitle('Could not connect to selected database!'); - $errorStatus->setMessage( - sprintf('Could not connect to database "%s"', $postValues['existing']) - . '! Make sure it really exists and your database user has the permissions to select it!' - ); - $result[] = $errorStatus; - } - $isInitialInstallation = $configurationManager->getConfigurationValueByPath('SYS/isInitialInstallationInProgress'); - if (!$isInitialInstallation || empty($result)) { - $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $postValues['existing']; - } - // check if database charset is utf-8 - $defaultDatabaseCharset = $this->getDefaultDatabaseCharset(); - // also allow utf8mb4 - if (substr($defaultDatabaseCharset, 0, 4) !== 'utf8') { - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); - $errorStatus->setTitle('Invalid Charset'); - $errorStatus->setMessage( - 'Your database uses character set "' . $defaultDatabaseCharset . '", ' . - 'but only "utf8" is supported with TYPO3. You probably want to change this before proceeding.' - ); - $result[] = $errorStatus; - $canProceed = false; + $status = $this->checkExistingDatabase($postValues['existing']); + if ($status instanceof ErrorStatus) { + return [ $status ]; } } else { - /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */ - $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class); + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); $errorStatus->setTitle('No Database selected'); $errorStatus->setMessage('You must select a database.'); - $result[] = $errorStatus; + return [ $errorStatus ]; } - - if ($canProceed && !empty($localConfigurationPathValuePairs)) { - $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs); - } - - return $result; + return []; } /** @@ -135,16 +69,16 @@ class DatabaseSelect extends AbstractStepAction */ public function needsExecution() { - $this->initializeDatabaseConnection(); $result = true; if ((string)$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] !== '') { - $this->databaseConnection->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname']); try { - $selectResult = $this->databaseConnection->sql_select_db(); - if ($selectResult === true) { + $pingResult = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME) + ->ping(); + if ($pingResult === true) { $result = false; } - } catch (\RuntimeException $e) { + } catch (DBALException $e) { } } return $result; @@ -157,9 +91,10 @@ class DatabaseSelect extends AbstractStepAction */ protected function executeAction() { - /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */ - $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class); - $isInitialInstallationInProgress = $configurationManager->getConfigurationValueByPath('SYS/isInitialInstallationInProgress'); + /** @var $configurationManager ConfigurationManager */ + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $isInitialInstallationInProgress = $configurationManager + ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress'); $this->view->assign('databaseList', $this->getDatabaseList($isInitialInstallationInProgress)); $this->view->assign('isInitialInstallationInProgress', $isInitialInstallationInProgress); $this->assignSteps(); @@ -174,45 +109,44 @@ class DatabaseSelect extends AbstractStepAction */ protected function getDatabaseList($initialInstallation) { - $this->initializeDatabaseConnection(); - $databaseArray = $this->databaseConnection->admin_get_dbs(); - // Remove mysql organizational tables from database list - $reservedDatabaseNames = array('mysql', 'information_schema', 'performance_schema'); + $connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]; + unset($connectionParams['dbname']); + + // Establishing the connection using the Doctrine DriverManager directly + // as we need a connection without selecting a database right away. Otherwise + // an invalid database name would lead to exceptions which would prevent + // changing the currently configured database. + $connection = DriverManager::getConnection($connectionParams); + $databaseArray = $connection->getSchemaManager()->listDatabases(); + $connection->close(); + + // Remove organizational tables from database list + $reservedDatabaseNames = ['mysql', 'information_schema', 'performance_schema']; $allPossibleDatabases = array_diff($databaseArray, $reservedDatabaseNames); // If we are upgrading we show *all* databases the user has access to if ($initialInstallation === false) { return $allPossibleDatabases; - } else { - // In first installation we show all databases but disable not empty ones (with tables) - $databases = array(); - foreach ($allPossibleDatabases as $database) { - $this->databaseConnection->setDatabaseName($database); - $this->databaseConnection->sql_select_db(); - $existingTables = $this->databaseConnection->admin_get_tables(); - $databases[] = array( - 'name' => $database, - 'tables' => count($existingTables), - ); - } - return $databases; } - } - /** - * Initialize database connection - * - * @return void - */ - protected function initializeDatabaseConnection() - { - $this->databaseConnection = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\DatabaseConnection::class); - $this->databaseConnection->setDatabaseUsername($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user']); - $this->databaseConnection->setDatabasePassword($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password']); - $this->databaseConnection->setDatabaseHost($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host']); - $this->databaseConnection->setDatabasePort($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port']); - $this->databaseConnection->setDatabaseSocket($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket']); - $this->databaseConnection->sql_pconnect(); + // In first installation we show all databases but disable not empty ones (with tables) + $databases = []; + foreach ($allPossibleDatabases as $databaseName) { + // Reestablising the connection for each database since there is no + // portable way to switch databases on the same Doctrine connection. + // Directly using the Doctrine DriverManager here to avoid messing with + // the $GLOBALS database configuration array. + $connectionParams['dbname'] = $databaseName; + $connection = DriverManager::getConnection($connectionParams); + + $databases[] = [ + 'name' => $databaseName, + 'tables' => count($connection->getSchemaManager()->listTableNames()), + ]; + $connection->close(); + } + + return $databases; } /** @@ -229,21 +163,134 @@ class DatabaseSelect extends AbstractStepAction /** * Retrieves the default character set of the database. * + * @todo this function is MySQL specific. If the core has migrated to Doctrine it should be reexamined + * whether this function and the check in $this->checkExistingDatabase could be deleted and utf8 otherwise + * enforced (guaranteeing compatability with other database servers). + * + * @param string $dbName * @return string */ - protected function getDefaultDatabaseCharset() + protected function getDefaultDatabaseCharset(string $dbName): string { - $result = $this->databaseConnection->admin_query('SHOW VARIABLES LIKE "character_set_database"'); - $row = $this->databaseConnection->sql_fetch_assoc($result); + $connection = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); + $queryBuilder = $connection->createQueryBuilder(); + $defaultDatabaseCharset = $queryBuilder->select('DEFAULT_CHARACTER_SET_NAME') + ->from('information_schema.SCHEMATA') + ->where( + $queryBuilder->expr()->eq('SCHEMA_NAME', $queryBuilder->quote($dbName)) + ) + ->setMaxResults(1) + ->execute() + ->fetchColumn(); - $key = $row['Variable_name']; - $value = $row['Value']; - $databaseCharset = ''; + return (string)$defaultDatabaseCharset; + } + + /** + * Creates a new database on the default connection + * + * @param string $dbName name of database + * + * @return \TYPO3\CMS\Install\Status\StatusInterface + */ + protected function createNewDatabase($dbName) + { + if (!$this->isValidDatabaseName($dbName)) { + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); + $errorStatus->setTitle('Database name not valid'); + $errorStatus->setMessage( + 'Given database name must be shorter than fifty characters' . + ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)' . + ' and underscores (_).' + ); + + return $errorStatus; + } + + try { + GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME) + ->getSchemaManager() + ->createDatabase($dbName); + GeneralUtility::makeInstance(ConfigurationManager::class) + ->setLocalConfigurationValueByPath('DB/Connections/Default/dbname', $dbName); + } catch (DBALException $e) { + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); + $errorStatus->setTitle('Unable to create database'); + $errorStatus->setMessage( + 'Database with name "' . $dbName . '" could not be created.' . + ' Either your database name contains a reserved keyword or your database' . + ' user does not have sufficient permissions to create it or the database already exists.' . + ' Please choose an existing (empty) database, choose another name or contact administration.' + ); + return $errorStatus; + } + + return GeneralUtility::makeInstance(OkStatus::class); + } + + /** + * Checks whether an existing database on the default connection + * can be used for a TYPO3 installation. The database name is only + * persisted to the local configuration if the database is empty. + * + * @param string $dbName name of the database + * @return \TYPO3\CMS\Install\Status\StatusInterface + */ + protected function checkExistingDatabase($dbName) + { + $result = GeneralUtility::makeInstance(OkStatus::class); + $localConfigurationPathValuePairs = []; + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $isInitialInstallation = $configurationManager + ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress'); + + $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['dbname'] = $dbName; + try { + $connection = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); - if ($key == 'character_set_database') { - $databaseCharset = $value; + if ($isInitialInstallation && !empty($connection->getSchemaManager()->listTableNames())) { + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); + $errorStatus->setTitle('Selected database is not empty!'); + $errorStatus->setMessage( + sprintf('Cannot use database "%s"', $dbName) + . ', because it already contains tables. ' + . 'Please select a different database or choose to create one!' + ); + $result = $errorStatus; + } + } catch (\Exception $e) { + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); + $errorStatus->setTitle('Could not connect to selected database!'); + $errorStatus->setMessage( + sprintf('Could not connect to database "%s"', $dbName) + . '! Make sure it really exists and your database user has the permissions to select it!' + ); + $result = $errorStatus; } - return $databaseCharset; + if ($result instanceof OkStatus) { + $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $dbName; + } + + // check if database charset is utf-8 - also allow utf8mb4 + $defaultDatabaseCharset = $this->getDefaultDatabaseCharset($dbName); + if (substr($defaultDatabaseCharset, 0, 4) !== 'utf8') { + $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class); + $errorStatus->setTitle('Invalid Charset'); + $errorStatus->setMessage( + 'Your database uses character set "' . $defaultDatabaseCharset . '", ' . + 'but only "utf8" is supported with TYPO3. You probably want to change this before proceeding.' + ); + $result = $errorStatus; + } + + if ($result instanceof OkStatus && !empty($localConfigurationPathValuePairs)) { + $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs); + } + + return $result; } } -- GitLab