[BUGFIX] Update complete database after extension installation 29/57429/11
authorNicole Cordes <typo3@cordes.co>
Sun, 1 Jul 2018 14:54:19 +0000 (16:54 +0200)
committerHelmut Hummel <typo3@helhum.io>
Wed, 4 Jul 2018 19:51:11 +0000 (21:51 +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/57429
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Helmut Hummel <typo3@helhum.io>
Tested-by: Helmut Hummel <typo3@helhum.io>
Reviewed-by: Daniel Goerz <ervaude@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
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 8f75055..28d2dd0 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 d037b0a..135e5ce 100644 (file)
@@ -327,15 +327,4 @@ class CategoryRegistryTest extends UnitTestCase
             substr_count($GLOBALS['TCA'][$this->tables['first']]['types']['newtypeafterfirstadd']['showitem'], '--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category')
         );
     }
-
-    /**
-     * @test
-     */
-    public function addAddsOnlyOneSqlString(): void
-    {
-        $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 5a8db9a..3da94cb 100644 (file)
@@ -170,7 +170,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 f5af06f..4425c45 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 (!isset($resolvedDependencies['installed']) || !is_array($resolvedDependencies['installed'])) {
                 $resolvedDependencies['installed'] = [];
index b94c742..7d274e8 100644 (file)
@@ -150,25 +150,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');
         }
-        // TODO: Should be possible to move this call to processExtensionSetup.
-        // TODO: We need to check why our acceptance test on postgress fails then
-        $this->saveDefaultConfiguration($extensionKey);
         $this->reloadCaches();
-        $this->processExtensionSetup($extensionKey);
+        $this->updateDatabase($extensionKeys);
 
-        $this->emitAfterExtensionInstallSignal($extensionKey);
+        foreach ($extensionKeys as $extensionKey) {
+            $this->processExtensionSetup($extensionKey);
+            $this->emitAfterExtensionInstallSignal($extensionKey);
+        }
     }
 
     /**
@@ -179,8 +187,8 @@ 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->importStaticSqlFile($extension['siteRelPath']);
+        $this->importT3DFile($extension['siteRelPath']);
     }
 
     /**
@@ -375,20 +383,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
@@ -435,6 +429,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 2c5fab9..2a57865 100644 (file)
@@ -54,7 +54,8 @@ class InstallUtilityTest extends UnitTestCase
     {
         $this->extensionKey = 'dummy';
         $this->extensionData = [
-            'key' => $this->extensionKey
+            'key' => $this->extensionKey,
+            'siteRelPath' => '',
         ];
         $this->installMock = $this->getAccessibleMock(
             InstallUtility::class,
@@ -62,8 +63,9 @@ class InstallUtilityTest extends UnitTestCase
                 'isLoaded',
                 'loadExtension',
                 'unloadExtension',
-                'processDatabaseUpdates',
-                'processRuntimeDatabaseUpdates',
+                'updateDatabase',
+                'importStaticSqlFile',
+                'importT3DFile',
                 'reloadCaches',
                 'processCachingFrameworkUpdates',
                 'saveDefaultConfiguration',
@@ -71,7 +73,7 @@ class InstallUtilityTest extends UnitTestCase
                 'enrichExtensionWithDetails',
                 'ensureConfiguredDirectoriesExist',
                 'importInitialFiles',
-                'emitAfterExtensionInstallSignal'
+                'emitAfterExtensionInstallSignal',
             ],
             [],
             '',
@@ -128,11 +130,11 @@ class InstallUtilityTest extends 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(CacheManager::class)->getMock();
         $cacheManagerMock->expects($this->once())->method('flushCachesInGroup');
@@ -197,7 +199,7 @@ class InstallUtilityTest extends 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);
     }
 
     /**
@@ -208,8 +210,8 @@ class InstallUtilityTest extends UnitTestCase
         $cacheManagerMock = $this->getMockBuilder(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 6fff4aa..b8f0899 100644 (file)
@@ -25,18 +25,6 @@ if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_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);
 }
 
index 8bcdb50..0b7106e 100644 (file)
@@ -148,9 +148,7 @@ abstract class AbstractUpdate
         $installUtility = GeneralUtility::makeInstance(
             \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class
         );
-        foreach ($extensionKeys as $extension) {
-            $installUtility->install($extension);
-        }
+        $installUtility->install($extensionKeys);
     }
 
     /**