[BUGFIX] Update complete database after extension installation 86/57486/3
authorNicole Cordes <typo3@cordes.co>
Sun, 1 Jul 2018 14:54:19 +0000 (16:54 +0200)
committerHelmut Hummel <typo3@helhum.io>
Sat, 7 Jul 2018 21:29:02 +0000 (23:29 +0200)
If an extension and its dependencies get installed, the whole
database needs to be updated instead of executing each extensions
SQL on its own.

Resolves: #79094
Releases: master, 8.7
Change-Id: I9a870e0efb6af241eeae563adbaa14af100edaec
Reviewed-on: https://review.typo3.org/57486
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Helmut Hummel <typo3@helhum.io>
Tested-by: Helmut Hummel <typo3@helhum.io>
typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php
typo3/sysext/core/Classes/Category/CategoryRegistry.php
typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php
typo3/sysext/extensionmanager/Classes/Controller/ActionController.php
typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php
typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php
typo3/sysext/extensionmanager/ext_localconf.php
typo3/sysext/install/Classes/Updates/AbstractUpdate.php

index 54e6725..3205531 100644 (file)
@@ -48,20 +48,6 @@ class DatabaseSchemaService
      * tables definitions string
      *
      * @param array $sqlString
-     * @param string $extensionKey
-     * @return array
-     */
-    public function addCachingFrameworkRequiredDatabaseSchemaForInstallUtility(array $sqlString, $extensionKey)
-    {
-        $sqlString[] = $this->getCachingFrameworkRequiredDatabaseSchema();
-        return [$sqlString, $extensionKey];
-    }
-
-    /**
-     * A slot method to inject the required caching framework database tables to the
-     * tables definitions string
-     *
-     * @param array $sqlString
      * @return array
      */
     public function addCachingFrameworkRequiredDatabaseSchemaForSqlExpectedSchemaService(array $sqlString)
index 7014abb..1f547ad 100644 (file)
@@ -443,20 +443,6 @@ class CategoryRegistry implements SingletonInterface
     }
 
     /**
-     * A slot method to inject the required category database fields of an
-     * extension to the tables definition string
-     *
-     * @param array $sqlString
-     * @param string $extensionKey
-     * @return array
-     */
-    public function addExtensionCategoryDatabaseSchemaToTablesDefinition(array $sqlString, $extensionKey)
-    {
-        $sqlString[] = $this->getDatabaseTableDefinition($extensionKey);
-        return ['sqlString' => $sqlString, 'extensionKey' => $extensionKey];
-    }
-
-    /**
      * @return LanguageService
      */
     protected function getLanguageService()
index d4d7f6e..582e0e6 100644 (file)
@@ -323,15 +323,4 @@ class CategoryRegistryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCas
             substr_count($GLOBALS['TCA'][$this->tables['first']]['types']['newtypeafterfirstadd']['showitem'], '--div--;LLL:EXT:lang/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category')
         );
     }
-
-    /**
-     * @test
-     */
-    public function addAddsOnlyOneSqlString()
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories1');
-        $this->subject->add('text_extension_b', $this->tables['first'], 'categories1', [], true);
-        $sqlData = $this->subject->addExtensionCategoryDatabaseSchemaToTablesDefinition([], 'text_extension_a');
-        $this->assertEmpty($sqlData['sqlString'][0]);
-    }
 }
index f3d717e..62cd8ad 100644 (file)
@@ -190,7 +190,7 @@ class ActionController extends AbstractController
         $registry = GeneralUtility::makeInstance(Registry::class);
         $registry->remove('extensionDataImport', $registryKey);
 
-        $this->installUtility->processDatabaseUpdates($extension);
+        $this->installUtility->processExtensionSetup($extension);
 
         $this->redirect('index', 'List');
     }
index 2b1201f..4047c38 100644 (file)
@@ -357,12 +357,13 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface
      */
     protected function installDependencies(array $installQueue)
     {
-        if (!empty($installQueue)) {
-            $this->emitWillInstallExtensionsSignal($installQueue);
+        if (empty($installQueue)) {
+            return [];
         }
+        $this->emitWillInstallExtensionsSignal($installQueue);
         $resolvedDependencies = [];
+        $this->installUtility->install(...array_keys($installQueue));
         foreach ($installQueue as $extensionKey => $_) {
-            $this->installUtility->install($extensionKey);
             $this->emitHasInstalledExtensionSignal($extensionKey);
             if (!is_array($resolvedDependencies['installed'])) {
                 $resolvedDependencies['installed'] = [];
index 3f9db8a..4a759a1 100644 (file)
@@ -174,22 +174,33 @@ class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
      * Helper function to install an extension
      * also processes db updates and clears the cache if the extension asks for it
      *
-     * @param string $extensionKey
+     * @param array $extensionKeys
      * @throws ExtensionManagerException
      */
-    public function install($extensionKey)
+    public function install(...$extensionKeys)
     {
-        $extension = $this->enrichExtensionWithDetails($extensionKey, false);
-        $this->loadExtension($extensionKey);
-        if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
+        $flushCaches = false;
+        foreach ($extensionKeys as $extensionKey) {
+            $this->loadExtension($extensionKey);
+            $extension = $this->enrichExtensionWithDetails($extensionKey, false);
+            $this->saveDefaultConfiguration($extensionKey);
+            if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
+                $flushCaches = true;
+            }
+        }
+
+        if ($flushCaches) {
             $this->cacheManager->flushCaches();
         } else {
             $this->cacheManager->flushCachesInGroup('system');
         }
         $this->reloadCaches();
-        $this->processExtensionSetup($extensionKey);
+        $this->updateDatabase($extensionKeys);
 
-        $this->emitAfterExtensionInstallSignal($extensionKey);
+        foreach ($extensionKeys as $extensionKey) {
+            $this->processExtensionSetup($extensionKey);
+            $this->emitAfterExtensionInstallSignal($extensionKey);
+        }
     }
 
     /**
@@ -199,10 +210,9 @@ class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
     {
         $extension = $this->enrichExtensionWithDetails($extensionKey, false);
         $this->ensureConfiguredDirectoriesExist($extension);
-        $this->importInitialFiles($extension['siteRelPath'], $extensionKey);
-        $this->processDatabaseUpdates($extension);
-        $this->processRuntimeDatabaseUpdates($extensionKey);
-        $this->saveDefaultConfiguration($extensionKey);
+        $this->importInitialFiles($extension['siteRelPath'] ?? '', $extensionKey);
+        $this->importStaticSqlFile($extension['siteRelPath']);
+        $this->importT3DFile($extension['siteRelPath']);
     }
 
     /**
@@ -397,20 +407,6 @@ class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
-     * Gets all database updates due to runtime configuration, like caching framework or
-     * category api for example
-     *
-     * @param string $extensionKey
-     */
-    protected function processRuntimeDatabaseUpdates($extensionKey)
-    {
-        $sqlString = $this->emitTablesDefinitionIsBeingBuiltSignal($extensionKey);
-        if (!empty($sqlString)) {
-            $this->updateDbWithExtTablesSql(implode(LF . LF . LF . LF, $sqlString));
-        }
-    }
-
-    /**
      * Emits a signal to manipulate the tables definitions
      *
      * @param string $extensionKey
@@ -458,6 +454,39 @@ class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
+     * Executes all safe database statements.
+     * Tables and fields are created and altered. Nothing gets deleted or renamed here.
+     *
+     * @param array $extensionKeys
+     */
+    protected function updateDatabase(array $extensionKeys)
+    {
+        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
+        $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
+        $sqlStatements = [];
+        $sqlStatements[] = $sqlReader->getTablesDefinitionString();
+        foreach ($extensionKeys as $extensionKey) {
+            $sqlStatements += $this->emitTablesDefinitionIsBeingBuiltSignal($extensionKey);
+        }
+        $sqlStatements = $sqlReader->getCreateTableStatementArray(implode(LF . LF, array_filter($sqlStatements)));
+        $updateStatements = $schemaMigrator->getUpdateSuggestions($sqlStatements);
+
+        $updateStatements = array_merge_recursive(...array_values($updateStatements));
+        $selectedStatements = [];
+        foreach (['add', 'change', 'create_table', 'change_table'] as $action) {
+            if (empty($updateStatements[$action])) {
+                continue;
+            }
+            $selectedStatements = array_merge(
+                $selectedStatements,
+                array_combine(array_keys($updateStatements[$action]), array_fill(0, count($updateStatements[$action]), true))
+            );
+        }
+
+        $schemaMigrator->migrate($sqlStatements, $selectedStatements);
+    }
+
+    /**
      * Save default configuration of an extension
      *
      * @param string $extensionKey
index 3d64c7c..0172ed1 100644 (file)
@@ -45,7 +45,8 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     {
         $this->extensionKey = 'dummy';
         $this->extensionData = [
-            'key' => $this->extensionKey
+            'key' => $this->extensionKey,
+            'siteRelPath' => '',
         ];
         $this->installMock = $this->getAccessibleMock(
             \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class,
@@ -53,8 +54,9 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                 'isLoaded',
                 'loadExtension',
                 'unloadExtension',
-                'processDatabaseUpdates',
-                'processRuntimeDatabaseUpdates',
+                'updateDatabase',
+                'importStaticSqlFile',
+                'importT3DFile',
                 'reloadCaches',
                 'processCachingFrameworkUpdates',
                 'saveDefaultConfiguration',
@@ -62,7 +64,7 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                 'enrichExtensionWithDetails',
                 'ensureConfiguredDirectoriesExist',
                 'importInitialFiles',
-                'emitAfterExtensionInstallSignal'
+                'emitAfterExtensionInstallSignal',
             ],
             [],
             '',
@@ -119,11 +121,11 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * @test
      */
-    public function installCallsProcessRuntimeDatabaseUpdates()
+    public function installCallsUpdateDatabase()
     {
         $this->installMock->expects($this->once())
-            ->method('processRuntimeDatabaseUpdates')
-            ->with($this->extensionKey);
+            ->method('updateDatabase')
+            ->with([$this->extensionKey]);
 
         $cacheManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Cache\CacheManager::class)->getMock();
         $cacheManagerMock->expects($this->once())->method('flushCachesInGroup');
@@ -188,7 +190,7 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cacheManagerMock->expects($this->once())->method('flushCachesInGroup');
         $this->installMock->_set('cacheManager', $cacheManagerMock);
         $this->installMock->expects($this->once())->method('reloadCaches');
-        $this->installMock->install('dummy');
+        $this->installMock->install($this->extensionKey);
     }
 
     /**
@@ -199,8 +201,8 @@ class InstallUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cacheManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Cache\CacheManager::class)->getMock();
         $cacheManagerMock->expects($this->once())->method('flushCachesInGroup');
         $this->installMock->_set('cacheManager', $cacheManagerMock);
-        $this->installMock->expects($this->once())->method('saveDefaultConfiguration')->with('dummy');
-        $this->installMock->install('dummy');
+        $this->installMock->expects($this->once())->method('saveDefaultConfiguration')->with($this->extensionKey);
+        $this->installMock->install($this->extensionKey);
     }
 
     /**
index 4e75c43..c275013 100644 (file)
@@ -1,6 +1,5 @@
 <?php
 defined('TYPO3_MODE') or die();
-
 // Register extension list update task
 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['extensionmanager'], ['allowed_classes' => false]);
 if (empty($extConf['offlineMode'])) {
@@ -11,7 +10,6 @@ if (empty($extConf['offlineMode'])) {
         'additionalFields' => '',
     ];
 }
-
 if (TYPO3_MODE === 'BE') {
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = \TYPO3\CMS\Extensionmanager\Command\ExtensionCommandController::class;
     if (!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL)) {
@@ -22,20 +20,7 @@ if (TYPO3_MODE === 'BE') {
             \TYPO3\CMS\Core\Package\PackageManager::class,
             'scanAvailablePackages'
         );
-        $signalSlotDispatcher->connect(
-            \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class,
-            'tablesDefinitionIsBeingBuilt',
-            \TYPO3\CMS\Core\Cache\DatabaseSchemaService::class,
-            'addCachingFrameworkRequiredDatabaseSchemaForInstallUtility'
-        );
-        $signalSlotDispatcher->connect(
-            \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class,
-            'tablesDefinitionIsBeingBuilt',
-            \TYPO3\CMS\Core\Category\CategoryRegistry::class,
-            'addExtensionCategoryDatabaseSchemaToTablesDefinition'
-        );
         unset($signalSlotDispatcher);
     }
 }
-
 unset($extConf);
index 7f83373..a64c630 100644 (file)
@@ -181,9 +181,7 @@ abstract class AbstractUpdate
         $installUtility = GeneralUtility::makeInstance(
             \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class
         );
-        foreach ($extensionKeys as $extension) {
-            $installUtility->install($extension);
-        }
+        $installUtility->install($extensionKeys);
     }
 
     /**