Skip to content

Commit

Permalink
[BUGFIX] Update complete database after extension installation
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
IchHabRecht authored and helhum committed Jul 4, 2018
1 parent 3130ee5 commit aa5ccf0
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 94 deletions.
14 changes: 0 additions & 14 deletions typo3/sysext/core/Classes/Cache/DatabaseSchemaService.php
Expand Up @@ -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
Expand Down
14 changes: 0 additions & 14 deletions typo3/sysext/core/Classes/Category/CategoryRegistry.php
Expand Up @@ -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
*/
Expand Down
11 changes: 0 additions & 11 deletions typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php
Expand Up @@ -327,15 +327,4 @@ public function addInitializesMissingTypes(): void
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]);
}
}
Expand Up @@ -170,7 +170,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');
}
Expand Down
Expand Up @@ -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 (!isset($resolvedDependencies['installed']) || !is_array($resolvedDependencies['installed'])) {
$resolvedDependencies['installed'] = [];
Expand Down
79 changes: 53 additions & 26 deletions typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
Expand Up @@ -150,25 +150,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');
}
// 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);
}
}

/**
Expand All @@ -179,8 +187,8 @@ public function processExtensionSetup($extensionKey)
$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']);
}

/**
Expand Down Expand Up @@ -374,20 +382,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
*
Expand Down Expand Up @@ -434,6 +428,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
*
Expand Down
Expand Up @@ -54,24 +54,26 @@ protected function setUp()
{
$this->extensionKey = 'dummy';
$this->extensionData = [
'key' => $this->extensionKey
'key' => $this->extensionKey,
'siteRelPath' => '',
];
$this->installMock = $this->getAccessibleMock(
InstallUtility::class,
[
'isLoaded',
'loadExtension',
'unloadExtension',
'processDatabaseUpdates',
'processRuntimeDatabaseUpdates',
'updateDatabase',
'importStaticSqlFile',
'importT3DFile',
'reloadCaches',
'processCachingFrameworkUpdates',
'saveDefaultConfiguration',
'getExtensionArray',
'enrichExtensionWithDetails',
'ensureConfiguredDirectoriesExist',
'importInitialFiles',
'emitAfterExtensionInstallSignal'
'emitAfterExtensionInstallSignal',
],
[],
'',
Expand Down Expand Up @@ -128,11 +130,11 @@ protected function createFakeExtension(): string
/**
* @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');
Expand Down Expand Up @@ -197,7 +199,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);
}

/**
Expand All @@ -208,8 +210,8 @@ public function installCallsSaveDefaultConfigurationWithExtensionKey()
$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);
}

/**
Expand Down
12 changes: 0 additions & 12 deletions typo3/sysext/extensionmanager/ext_localconf.php
Expand Up @@ -25,18 +25,6 @@
\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);
}

Expand Down
4 changes: 1 addition & 3 deletions typo3/sysext/install/Classes/Updates/AbstractUpdate.php
Expand Up @@ -148,9 +148,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);
}

/**
Expand Down

0 comments on commit aa5ccf0

Please sign in to comment.