[TASK] Update database schema as first and last update wizard 40/27240/8
authorStephan Großberndt <stephan@grossberndt.de>
Sat, 1 Feb 2014 17:23:01 +0000 (18:23 +0100)
committerMarkus Klein <klein.t3@mfc-linz.at>
Sun, 2 Mar 2014 13:24:19 +0000 (14:24 +0100)
Introduces two new upgrade wizards in the Install tool.

The first wizard - added as first step of the upgrade wizards - adds
tables, fields and keys to comply to the database schema. When this is
necessary no other wizards can be executed until these are created.

The second wizard - added as last step of the upgrade wizards - changes
tables, fields and keys to comply to the database schema. When other
upgrade wizards are available, this one is not available to make sure
they have all necessary fields.

In order to make sure they are added as first and last step they are
added in UpdateWizard instead of ext_localconf.php.

The former "Final step" is now optional and has been renamed to "Hint".
The buttons to start the update wizards from the list have been renamed
from "Next" to "Execute".

Resolves: #53890
Releases: 6.2
Change-Id: I866b558df3325acca3122bbd4e0c2285447fcdf3
Reviewed-on: https://review.typo3.org/27240
Reviewed-by: Markus Klein
Tested-by: Markus Klein
typo3/sysext/install/Classes/Controller/Action/Tool/UpgradeWizard.php
typo3/sysext/install/Classes/Updates/AbstractDatabaseSchemaUpdate.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/InitialDatabaseSchemaUpdate.php [new file with mode: 0644]
typo3/sysext/install/Resources/Private/Partials/Action/Tool/UpgradeWizard/ListUpdates.html

index 305681a..fe77b7f 100644 (file)
@@ -33,6 +33,13 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class UpgradeWizard extends Action\AbstractAction implements Action\ActionInterface {
 
        /**
+        * There are tables and fields missing in the database
+        *
+        * @var bool
+        */
+       protected $needsInitialUpdateDatabaseSchema = FALSE;
+
+       /**
         * Handle this action
         *
         * @return string content
@@ -43,6 +50,22 @@ class UpgradeWizard extends Action\AbstractAction implements Action\ActionInterf
                // ext_localconf, db and ext_tables must be loaded for the upgrade wizards
                $this->loadExtLocalconfDatabaseAndExtTables();
 
+               // To make sure initialUpdateDatabaseSchema is first wizard, it is added here instead of ext_localconf.php
+               $initialUpdateDatabaseSchemaUpdateObject = $this->getUpgradeObjectInstance('TYPO3\\CMS\\Install\\Updates\\InitialDatabaseSchemaUpdate', 'initialUpdateDatabaseSchema');
+               if ($initialUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
+                       $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
+                               array('initialUpdateDatabaseSchema' => 'TYPO3\\CMS\\Install\\Updates\\InitialDatabaseSchemaUpdate'),
+                               $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
+                       );
+                       $this->needsInitialUpdateDatabaseSchema = TRUE;
+               }
+
+               // To make sure finalUpdateDatabaseSchema is last wizard, it is added here instead of ext_localconf.php
+               $finalUpdateDatabaseSchemaUpdateObject = $this->getUpgradeObjectInstance('TYPO3\\CMS\\Install\\Updates\\FinalDatabaseSchemaUpdate', 'finalUpdateDatabaseSchema');
+               if ($finalUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
+                       $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['finalUpdateDatabaseSchema'] = 'TYPO3\\CMS\\Install\\Updates\\FinalDatabaseSchemaUpdate';
+               }
+
                // Perform silent cache framework table upgrades
                $this->silentCacheFrameworkTableSchemaMigration();
 
@@ -90,8 +113,13 @@ class UpgradeWizard extends Action\AbstractAction implements Action\ActionInterf
                                        'explanation' => $explanation,
                                        'renderNext' => FALSE,
                                );
-                               // There are upgrade wizards that only show text and don't want to be executed
-                               if ($updateObject->shouldRenderNextButton()) {
+                               if ($identifier === 'initialUpdateDatabaseSchema') {
+                                       $availableUpdates['initialUpdateDatabaseSchema']['renderNext'] = $this->needsInitialUpdateDatabaseSchema;
+                               } elseif ($identifier === 'finalUpdateDatabaseSchema') {
+                                       // Okay to check here because finalUpdateDatabaseSchema is last element in array
+                                       $availableUpdates['finalUpdateDatabaseSchema']['renderNext'] = count($availableUpdates) === 1;
+                               } elseif (!$this->needsInitialUpdateDatabaseSchema && $updateObject->shouldRenderNextButton()) {
+                                       // There are upgrade wizards that only show text and don't want to be executed
                                        $availableUpdates[$identifier]['renderNext'] = TRUE;
                                }
                        }
@@ -236,7 +264,10 @@ class UpgradeWizard extends Action\AbstractAction implements Action\ActionInterf
                        // Find the current update wizard, and then start validating the next ones
                        if ($currentObj->getIdentifier() == $identifier) {
                                $isPreviousRecord = FALSE;
-                               continue;
+                               // For the updateDatabaseSchema-wizards verify they do not have to be executed again
+                               if ($identifier !== 'initialUpdateDatabaseSchema' && $identifier !== 'finalUpdateDatabaseSchema') {
+                                       continue;
+                               }
                        }
                        if (!$isPreviousRecord) {
                                $nextUpgradeWizard = $this->getUpgradeObjectInstance($className, $identifier);
diff --git a/typo3/sysext/install/Classes/Updates/AbstractDatabaseSchemaUpdate.php b/typo3/sysext/install/Classes/Updates/AbstractDatabaseSchemaUpdate.php
new file mode 100644 (file)
index 0000000..ced90d8
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Stephan Großberndt <stephan@grossberndt.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the text file GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Contains the update class to create and alter tables, fields and keys to comply to the database schema
+ *
+ * @author Stephan Großberndt <stephan@grossberndt.de>
+ */
+abstract class AbstractDatabaseSchemaUpdate extends AbstractUpdate {
+
+       /**
+        * @var string
+        */
+       protected $title;
+
+       /**
+        * @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService
+        */
+       protected $schemaMigrationService;
+
+       /**
+        * @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService
+        */
+       protected $expectedSchemaService;
+
+       /**
+        * Constructor function.
+        */
+       public function __construct() {
+               $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
+               $this->schemaMigrationService = $objectManager->get('TYPO3\\CMS\\Install\\Service\\SqlSchemaMigrationService');
+               $this->expectedSchemaService = $objectManager->get('TYPO3\\CMS\\Install\\Service\\SqlExpectedSchemaService');
+       }
+
+       /**
+        * Compare current and expected database schemas and return the database differences
+        *
+        * @return array database differences
+        */
+       protected function getDatabaseDifferences() {
+               $expectedSchema = $this->expectedSchemaService->getExpectedDatabaseSchema();
+               $currentSchema = $this->schemaMigrationService->getFieldDefinitions_database();
+
+               // Difference from expected to current
+               return $this->schemaMigrationService->getDatabaseExtra($expectedSchema, $currentSchema);
+       }
+
+}
diff --git a/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php b/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php
new file mode 100644 (file)
index 0000000..6aa9228
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Stephan Großberndt <stephan@grossberndt.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the text file GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Contains the update class to create and alter tables, fields and keys to comply to the database schema
+ *
+ * @author Stephan Großberndt <stephan@grossberndt.de>
+ */
+class FinalDatabaseSchemaUpdate extends AbstractDatabaseSchemaUpdate {
+
+       /**
+        * Constructor function.
+        */
+       public function __construct() {
+               parent::__construct();
+               $this->title = 'Update database schema: Modify tables and fields';
+       }
+
+       /**
+        * Checks if an update is needed
+        *
+        * @param string &$description The description for the update
+        * @return bool TRUE if an update is needed, FALSE otherwise
+        */
+       public function checkForUpdate(&$description) {
+               $description = 'There are tables or fields in the database which need to be changed.<br /><br />' .
+               'This update wizard can be run only when there are no other update wizards left to make sure they have all needed fields unchanged.<br /><br />' .
+               'If you want to apply changes selectively, <a href="Install.php?install[action]=importantActions&amp;install[context]={context}&amp;install[controller]=tool">go to Database Analyzer</a>.';
+
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateSuggestions = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               return isset($updateSuggestions['change']);
+       }
+
+       /**
+        * Second step: Show tables, fields and keys to create or update
+        *
+        * @param string $inputPrefix input prefix, all names of form fields have to start with this. Append custom name in [ ... ]
+        * @return string HTML output
+        */
+       public function getUserInput($inputPrefix) {
+               $result = '';
+
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateSuggestions = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               if (!isset($updateSuggestions['change'])) {
+                       return $result;
+               }
+
+               $fieldsList = '
+                       <p>
+                               Change the following fields in tables:
+                       </p>
+                       <fieldset>
+                               <ol class="t3-install-form-label-after">%s</ol>
+                       </fieldset>';
+               $keysList = '
+                       <p>
+                               Change the following keys in tables:
+                       </p>
+                       <fieldset>
+                               <ol class="t3-install-form-label-after">%s</ol>
+                       </fieldset>';
+               $item = '
+                       <li class="labelAfter">
+                               <label><strong>%1$s</strong>: %2$s</label>
+                       </li>';
+
+               $fieldItems = array();
+               $keyItems = array();
+               foreach ($databaseDifferences['diff'] as $tableName => $difference) {
+                       if ($difference['fields']) {
+                               $fieldNames = array();
+                               foreach ($difference['fields'] as $fieldName => $sql) {
+                                       $fieldNames[] = $fieldName;
+                               }
+                               $fieldItems[] = sprintf($item, $tableName, implode(', ', $fieldNames));
+                       }
+                       if ($difference['keys']) {
+                               $keyNames = array();
+                               foreach ($difference['keys'] as $keyName => $sql) {
+                                       $keyNames[] = $keyName;
+                               }
+                               $keyItems[] = sprintf($item, $tableName, implode(', ', $keyNames));
+                       }
+               }
+               if (!empty($fieldItems)) {
+                       $result .= sprintf($fieldsList, implode('', $fieldItems));
+               }
+               if (!empty($keyItems)) {
+                       $result .= sprintf($keysList, implode('', $keyItems));
+               }
+
+               return $result;
+       }
+
+
+       /**
+        * Performs the database update.
+        *
+        * @param array &$dbQueries Queries done in this update
+        * @param mixed &$customMessages Custom messages
+        * @return bool TRUE on success, FALSE on error
+        */
+       public function performUpdate(array &$dbQueries, &$customMessages) {
+               // First perform all add update statements to database
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateStatements = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               foreach ((array)$updateStatements['change'] as $query) {
+                       $GLOBALS['TYPO3_DB']->admin_query($query);
+                       $dbQueries[] = $query;
+                       if ($GLOBALS['TYPO3_DB']->sql_error()) {
+                               $customMessages = 'SQL-ERROR: ' . htmlspecialchars($GLOBALS['TYPO3_DB']->sql_error());
+                               return FALSE;
+                       }
+               }
+
+               return TRUE;
+       }
+}
diff --git a/typo3/sysext/install/Classes/Updates/InitialDatabaseSchemaUpdate.php b/typo3/sysext/install/Classes/Updates/InitialDatabaseSchemaUpdate.php
new file mode 100644 (file)
index 0000000..c7ebe3c
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Stephan Großberndt <stephan@grossberndt.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the text file GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Contains the update class to create tables, fields and keys to comply to the database schema
+ *
+ * @author Stephan Großberndt <stephan@grossberndt.de>
+ */
+class InitialDatabaseSchemaUpdate extends AbstractDatabaseSchemaUpdate {
+
+       /**
+        * Constructor function.
+        */
+       public function __construct() {
+               parent::__construct();
+               $this->title = 'Update database schema: Create tables and fields';
+       }
+
+       /**
+        * Checks if an update is needed
+        *
+        * @param string &$description The description for the update
+        * @return bool TRUE if an update is needed, FALSE otherwise
+        */
+       public function checkForUpdate(&$description) {
+               $description = 'There are tables or fields in the database which need to be created.<br /><br />' .
+               'You have to run this update wizard before you can run any other update wizard to make sure all needed tables and fields are present.';
+
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateSuggestions = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               return isset($updateSuggestions['create_table']) || isset($updateSuggestions['add']);
+       }
+
+       /**
+        * Second step: Show tables, fields and keys to be created
+        *
+        * @param string $inputPrefix input prefix, all names of form fields have to start with this. Append custom name in [ ... ]
+        * @return string HTML output
+        */
+       public function getUserInput($inputPrefix) {
+               $result = '';
+
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateSuggestions = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               if (isset($updateSuggestions['create_table'])) {
+                       $list = '
+                               <p>
+                                       Add the following tables:
+                               </p>
+                               <fieldset>
+                                       <ol class="t3-install-form-label-after">%s</ol>
+                               </fieldset>';
+                       $item = '
+                               <li class="labelAfter">
+                                       <label><strong>%1$s</strong></label>
+                               </li>';
+
+                       $items = array();
+                       foreach ($databaseDifferences['extra'] as $tableName => $difference) {
+                               if ($difference['whole_table'] == 1) {
+                                       $items[] = sprintf($item, $tableName);
+                               }
+                       }
+                       $result .= sprintf($list, implode('', $items));
+               }
+
+               if (isset($updateSuggestions['add'])) {
+                       $fieldsList = '
+                               <p>
+                                       Add the following fields to tables:
+                               </p>
+                               <fieldset>
+                                       <ol class="t3-install-form-label-after">%s</ol>
+                               </fieldset>';
+                       $keysList = '
+                               <p>
+                                       Add the following keys to tables:
+                               </p>
+                               <fieldset>
+                                       <ol class="t3-install-form-label-after">%s</ol>
+                               </fieldset>';
+                       $item = '
+                               <li class="labelAfter">
+                                       <label><strong>%1$s</strong>: %2$s</label>
+                               </li>';
+
+                       $fieldItems = array();
+                       $keyItems = array();
+                       foreach ($databaseDifferences['extra'] as $tableName => $difference) {
+                               if ($difference['whole_table'] != 1) {
+                                       if ($difference['fields']) {
+                                               $fieldNames = array();
+                                               foreach ($difference['fields'] as $fieldName => $sql) {
+                                                       $fieldNames[] = $fieldName;
+                                               }
+                                               $fieldItems[] = sprintf($item, $tableName, implode(', ', $fieldNames));
+                                       }
+                                       if ($difference['keys']) {
+                                               $keyNames = array();
+                                               foreach ($difference['keys'] as $keyName => $sql) {
+                                                       $keyNames[] = $keyName;
+                                               }
+                                               $keyItems[] = sprintf($item, $tableName, implode(', ', $keyNames));
+                                       }
+                               }
+                       }
+                       if (!empty($fieldItems)) {
+                               $result .= sprintf($fieldsList, implode('', $fieldItems));
+                       }
+                       if (!empty($keyItems)) {
+                               $result .= sprintf($keysList, implode('', $keyItems));
+                       }
+               }
+
+               return $result;
+       }
+
+
+       /**
+        * Performs the database update.
+        *
+        * @param array &$dbQueries Queries done in this update
+        * @param mixed &$customMessages Custom messages
+        * @return bool TRUE on success, FALSE on error
+        */
+       public function performUpdate(array &$dbQueries, &$customMessages) {
+
+               // First perform all add update statements to database
+               $databaseDifferences = $this->getDatabaseDifferences();
+               $updateStatements = $this->schemaMigrationService->getUpdateSuggestions($databaseDifferences);
+
+               foreach ((array)$updateStatements['create_table'] as $query) {
+                       $GLOBALS['TYPO3_DB']->admin_query($query);
+                       $dbQueries[] = $query;
+                       if ($GLOBALS['TYPO3_DB']->sql_error()) {
+                               $customMessages = 'SQL-ERROR: ' . htmlspecialchars($GLOBALS['TYPO3_DB']->sql_error());
+                               return FALSE;
+                       }
+               }
+
+               foreach ((array)$updateStatements['add'] as $query) {
+                       $GLOBALS['TYPO3_DB']->admin_query($query);
+                       $dbQueries[] = $query;
+                       if ($GLOBALS['TYPO3_DB']->sql_error()) {
+                               $customMessages = 'SQL-ERROR: ' . htmlspecialchars($GLOBALS['TYPO3_DB']->sql_error());
+                               return FALSE;
+                       }
+               }
+
+               return TRUE;
+       }
+}
index 23ef331..29e276f 100644 (file)
@@ -16,7 +16,7 @@
                                <f:if condition="{availableUpdate.renderNext}">
                                        <f:render
                                                partial="Action/Common/SubmitButton"
-                                               arguments="{name:'getUserInput', text:'Next'}"
+                                               arguments="{name:'getUserInput', text:'Execute'}"
                                        />
                                </f:if>
                                <hr />
        </f:else>
 </f:if>
 
-<h4>Final step</h4>
+<h4>Hint</h4>
 <p>
-       When all updates are done you should check your database for required updates.
-       <br />
-       Perform the database analyzer steps until no more changes are required.
+       When all updates are done you can check the database for tables and fields no longer required.
+       Perform the Database Analyzer steps until no more changes are required.
 </p>
-<a href="Install.php?install[action]=importantActions&install[context]={context}&install[controller]=tool">
-       Go to database analyzer
-</a>
\ No newline at end of file
+<a href="Install.php?install[action]=importantActions&amp;install[context]={context}&amp;install[controller]=tool">
+       Go to Database Analyzer
+</a>