[TASK] Install Tool: Show database errors on initial import 28/43028/9
authorMorton Jonuschat <m.jonuschat@mojocode.de>
Fri, 4 Sep 2015 13:52:05 +0000 (15:52 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Mon, 14 Sep 2015 09:24:28 +0000 (11:24 +0200)
Database error that occur during the initial install will not be
silently suppressed anymore. The user will be shown the statement and
the error reported by the database.

To halt the installation at this step the state of the initial data
import needs to be tracked and persisted. After all statements have been
processed successfully a key will be added to the system registry
marking the import as done.

Resolves: #51930
Releases: master
Change-Id: I21f9f85524344f2744ac70a54610775ae86c8ea2
Reviewed-on: http://review.typo3.org/43028
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Daniel Goerz <ervaude@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/FactoryConfiguration.php
typo3/sysext/install/Classes/Controller/Action/Step/DatabaseData.php

index 2ca5789..ed6fec4 100644 (file)
@@ -269,6 +269,7 @@ return array(
                ),
                'livesearch' => array(),        // Array: keywords used for commands to search for specific tables
                'isInitialInstallationInProgress' => FALSE,             // Boolean: If TRUE, the installation is 'in progress'. This value is handled within the install tool step installer internally.
+               'isInitialDatabaseImportDone' => TRUE,          // Boolean: If TRUE, the database import is finished. This value is handled within the install tool step installer internally.
                'clearCacheSystem' => FALSE,            // Boolean: If set, the toolbar menu entry for clearing system caches (core cache, class cache, etc.) is visible for admin users.
                'formEngine' => array(
                        'nodeRegistry' => array(), // Array: Registry to add or overwrite FormEngine nodes. Main key is a timestamp of the date when an entry is added, sub keys type, priority and class are required. Class must implement TYPO3\CMS\Backend\Form\NodeInterface.
index c562387..4cb9e7a 100644 (file)
@@ -38,6 +38,7 @@ return array(
        ),
        'SYS' => array(
                'isInitialInstallationInProgress' => TRUE,
+               'isInitialDatabaseImportDone' => FALSE,
                'sitename' => 'New TYPO3 site',
        ),
 );
index db6ef32..524a95b 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Install\Controller\Action\Step;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Install\Status\ErrorStatus;
+
 /**
  * Populate base tables, insert admin user, set install tool password
  */
@@ -22,13 +25,13 @@ class DatabaseData extends AbstractStepAction {
        /**
         * Import tables and data, create admin user, create install tool password
         *
-        * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
+        * @return \TYPO3\CMS\Install\Status\StatusInterface[]
         */
        public function execute() {
                $result = array();
 
-               /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
-               $configurationManager = $this->objectManager->get(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
+               /** @var ConfigurationManager $configurationManager */
+               $configurationManager = $this->objectManager->get(ConfigurationManager::class);
 
                $postValues = $this->postValues['values'];
 
@@ -37,7 +40,7 @@ class DatabaseData extends AbstractStepAction {
                // Check password and return early if not good enough
                $password = $postValues['password'];
                if (strlen($password) < 8) {
-                       $errorStatus = $this->objectManager->get(\TYPO3\CMS\Install\Status\ErrorStatus::class);
+                       $errorStatus = $this->objectManager->get(ErrorStatus::class);
                        $errorStatus->setTitle('Administrator password not secure enough!');
                        $errorStatus->setMessage(
                                'You are setting an important password here! It gives an attacker full control over your instance if cracked.' .
@@ -52,7 +55,10 @@ class DatabaseData extends AbstractStepAction {
                        $configurationManager->setLocalConfigurationValueByPath('SYS/sitename', $postValues['sitename']);
                }
 
-               $this->importDatabaseData();
+               $result = $this->importDatabaseData();
+               if (!empty($result)) {
+                       return $result;
+               }
 
                // Insert admin user
                $adminUserFields = array(
@@ -62,11 +68,23 @@ class DatabaseData extends AbstractStepAction {
                        'tstamp' => $GLOBALS['EXEC_TIME'],
                        'crdate' => $GLOBALS['EXEC_TIME']
                );
-               $this->getDatabaseConnection()->exec_INSERTquery('be_users', $adminUserFields);
+               if (FALSE === $this->getDatabaseConnection()->exec_INSERTquery('be_users', $adminUserFields)) {
+                       $errorStatus = $this->objectManager->get(ErrorStatus::class);
+                       $errorStatus->setTitle('Administrator account not created!');
+                       $errorStatus->setMessage(
+                               'The administrator account could not be created. The following error occurred:' . LF .
+                               $this->getDatabaseConnection()->sql_error()
+                       );
+                       $result[] = $errorStatus;
+                       return $result;
+               };
 
                // Set password as install tool password
                $configurationManager->setLocalConfigurationValueByPath('BE/installToolPassword', $this->getHashedPassword($password));
 
+               // Mark the initial import as done
+               $this->markImportDatabaseDone();
+
                return $result;
        }
 
@@ -76,10 +94,11 @@ class DatabaseData extends AbstractStepAction {
         * @return bool
         */
        public function needsExecution() {
-               $result = FALSE;
                $existingTables = $this->getDatabaseConnection()->admin_get_tables();
                if (empty($existingTables)) {
                        $result = TRUE;
+               } else {
+                       $result = !$this->isImportDatabaseDone();
                }
                return $result;
        }
@@ -97,9 +116,10 @@ class DatabaseData extends AbstractStepAction {
        /**
         * Create tables and import static rows
         *
-        * @return void
+        * @return \TYPO3\CMS\Install\Status\StatusInterface[]
         */
        protected function importDatabaseData() {
+               $result = array();
                // Will load ext_localconf and ext_tables. This is pretty safe here since we are
                // in first install (database empty), so it is very likely that no extension is loaded
                // that could trigger a fatal at this point.
@@ -116,23 +136,64 @@ class DatabaseData extends AbstractStepAction {
                $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(TRUE);
                $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, TRUE);
                list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, TRUE);
-
                $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
                $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
                $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
                $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
 
-               $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
-               $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
-               $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
+               foreach (array('add', 'change', 'create_table') as $action) {
+                       $updateStatus = $schemaMigrationService->performUpdateQueries($updateStatements[$action], $updateStatements[$action]);
+                       if ($updateStatus !== TRUE) {
+                               foreach ($updateStatus as $statementIdentifier => $errorMessage) {
+                                       $result[$updateStatements[$action][$statementIdentifier]] = $errorMessage;
+                               }
+                       }
+               }
 
-               foreach ($insertCount as $table => $count) {
-                       $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
-                       foreach ($insertStatements as $insertQuery) {
-                               $insertQuery = rtrim($insertQuery, ';');
-                               $database->admin_query($insertQuery);
+               if (empty($result)) {
+                       foreach ($insertCount as $table => $count) {
+                               $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
+                               foreach ($insertStatements as $insertQuery) {
+                                       $insertQuery = rtrim($insertQuery, ';');
+                                       $database->admin_query($insertQuery);
+                                       if ($database->sql_error()) {
+                                               $result[$insertQuery] = $database->sql_error();
+                                       }
+                               }
                        }
                }
+
+               foreach ($result as $statement => &$message) {
+                       $errorStatus = $this->objectManager->get(ErrorStatus::class);
+                       $errorStatus->setTitle('Database query failed!');
+                       $errorStatus->setMessage(
+                               'Query:' . LF .
+                               ' ' . $statement . LF .
+                               'Error:' . LF .
+                               ' ' . $message
+                       );
+                       $message = $errorStatus;
+               }
+
+               return array_values($result);
+       }
+
+       /**
+        * Persist the information that the initial import has been performed
+        */
+       protected function markImportDatabaseDone() {
+               $this->objectManager->get(ConfigurationManager::class)
+                       ->setLocalConfigurationValueByPath('SYS/isInitialDatabaseImportDone', TRUE);
+       }
+
+       /**
+        * Checks if the initial import has been performed
+        *
+        * @return bool
+        */
+       protected function isImportDatabaseDone() {
+               return $this->objectManager->get(ConfigurationManager::class)
+                       ->getLocalConfigurationValueByPath('SYS/isInitialDatabaseImportDone');
        }
 
 }