From a8d51b4ac7da0de3598b85fbddf855c339428edf Mon Sep 17 00:00:00 2001 From: Nicole Cordes Date: Sun, 1 Jul 2018 16:54:19 +0200 Subject: [PATCH] [BUGFIX] Update complete database after extension installation 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 Reviewed-by: Helmut Hummel Tested-by: Helmut Hummel --- .../Classes/Cache/DatabaseSchemaService.php | 14 ---- .../Classes/Category/CategoryRegistry.php | 14 ---- .../Unit/Category/CategoryRegistryTest.php | 11 --- .../Classes/Controller/ActionController.php | 2 +- .../Service/ExtensionManagementService.php | 7 +- .../Classes/Utility/InstallUtility.php | 79 +++++++++++++------ .../Tests/Unit/Utility/InstallUtilityTest.php | 22 +++--- .../sysext/extensionmanager/ext_localconf.php | 15 ---- .../Classes/Updates/AbstractUpdate.php | 4 +- 9 files changed, 72 insertions(+), 96 deletions(-) diff --git a/typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php b/typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php index 54e6725bcd04..32055317c679 100644 --- a/typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php +++ b/typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php @@ -43,20 +43,6 @@ public function getCachingFrameworkRequiredDatabaseSchema() return $tableDefinitions; } - /** - * A slot method to inject the required caching framework database tables to the - * 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 diff --git a/typo3/sysext/core/Classes/Category/CategoryRegistry.php b/typo3/sysext/core/Classes/Category/CategoryRegistry.php index 7014abbea0f2..1f547ada5f86 100644 --- a/typo3/sysext/core/Classes/Category/CategoryRegistry.php +++ b/typo3/sysext/core/Classes/Category/CategoryRegistry.php @@ -442,20 +442,6 @@ public function addCategoryDatabaseSchemaToTablesDefinition(array $sqlString) return ['sqlString' => $sqlString]; } - /** - * 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 */ diff --git a/typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php b/typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php index d4d7f6e4659c..582e0e6bacaa 100644 --- a/typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php +++ b/typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php @@ -323,15 +323,4 @@ public function addInitializesMissingTypes() 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]); - } } diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php index f3d717e16f44..62cd8adc75e3 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php @@ -190,7 +190,7 @@ protected function reloadExtensionDataAction($extension) $registry = GeneralUtility::makeInstance(Registry::class); $registry->remove('extensionDataImport', $registryKey); - $this->installUtility->processDatabaseUpdates($extension); + $this->installUtility->processExtensionSetup($extension); $this->redirect('index', 'List'); } diff --git a/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php b/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php index 2b1201f972e7..4047c38aa2d9 100644 --- a/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php +++ b/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php @@ -357,12 +357,13 @@ protected function uninstallDependenciesToBeUpdated(array $updateQueue) */ 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'] = []; diff --git a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php index 3f9db8a391fe..4a759a1bcf58 100644 --- a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php +++ b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php @@ -174,22 +174,33 @@ public function injectRegistry(\TYPO3\CMS\Core\Registry $registry) * 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 @@ public function processExtensionSetup($extensionKey) { $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']); } /** @@ -396,20 +406,6 @@ public function processDatabaseUpdates(array $extension) $this->importT3DFile($extension['siteRelPath']); } - /** - * 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 * @@ -457,6 +453,39 @@ protected function reloadOpcache() GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive(); } + /** + * 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 * diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php index 3d64c7c17e9a..0172ed1a9e31 100644 --- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php +++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php @@ -45,7 +45,8 @@ protected function setUp() { $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 @@ protected function setUp() 'isLoaded', 'loadExtension', 'unloadExtension', - 'processDatabaseUpdates', - 'processRuntimeDatabaseUpdates', + 'updateDatabase', + 'importStaticSqlFile', + 'importT3DFile', 'reloadCaches', 'processCachingFrameworkUpdates', 'saveDefaultConfiguration', @@ -62,7 +64,7 @@ protected function setUp() 'enrichExtensionWithDetails', 'ensureConfiguredDirectoriesExist', 'importInitialFiles', - 'emitAfterExtensionInstallSignal' + 'emitAfterExtensionInstallSignal', ], [], '', @@ -119,11 +121,11 @@ protected function createFakeExtension() /** * @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 @@ public function installCallsReloadCaches() $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 @@ public function installCallsSaveDefaultConfigurationWithExtensionKey() $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); } /** diff --git a/typo3/sysext/extensionmanager/ext_localconf.php b/typo3/sysext/extensionmanager/ext_localconf.php index 4e75c436be51..c275013df5e5 100644 --- a/typo3/sysext/extensionmanager/ext_localconf.php +++ b/typo3/sysext/extensionmanager/ext_localconf.php @@ -1,6 +1,5 @@ false]); if (empty($extConf['offlineMode'])) { @@ -11,7 +10,6 @@ '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 @@ \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); diff --git a/typo3/sysext/install/Classes/Updates/AbstractUpdate.php b/typo3/sysext/install/Classes/Updates/AbstractUpdate.php index 7f833732752a..a64c630a8025 100644 --- a/typo3/sysext/install/Classes/Updates/AbstractUpdate.php +++ b/typo3/sysext/install/Classes/Updates/AbstractUpdate.php @@ -181,9 +181,7 @@ protected function installExtensions(array $extensionKeys) $installUtility = GeneralUtility::makeInstance( \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class ); - foreach ($extensionKeys as $extension) { - $installUtility->install($extension); - } + $installUtility->install($extensionKeys); } /**