Commit 1db14cd5 authored by Christian Kuhn's avatar Christian Kuhn
Browse files

[TASK] Migrate upgrade wizards in install extension

Resolves: #86201
Related: #86172
Releases: master
Change-Id: I9d38ca04ee8a71ff6ffa325a2f0ce2c8c6cde183
Reviewed-on: https://review.typo3.org/58293


Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent c26c272b
......@@ -925,8 +925,7 @@ class UpgradeController extends AbstractController
$this->loadExtLocalconfDatabaseAndExtTables();
$upgradeWizardsService = new UpgradeWizardsService();
$identifier = $request->getParsedBody()['install']['identifier'];
$showDatabaseQueries = (int)$request->getParsedBody()['install']['values']['showDatabaseQueries'];
$messages = $upgradeWizardsService->executeWizard($identifier, $showDatabaseQueries);
$messages = $upgradeWizardsService->executeWizard($identifier);
return new JsonResponse([
'success' => true,
'status' => $messages,
......
......@@ -44,6 +44,9 @@ abstract class AbstractDownloadExtensionUpdate implements UpgradeWizardInterface
*/
protected $extension;
/**
* @param OutputInterface $output
*/
public function setOutput(OutputInterface $output): void
{
$this->output = $output;
......@@ -106,16 +109,12 @@ abstract class AbstractDownloadExtensionUpdate implements UpgradeWizardInterface
if ($isComposerMode && !$isExtensionAvailable) {
$updateSuccessful = false;
$this->output->writeln('<warning>The extension ' .
$extensionKey .
' can not be downloaded since ' .
'Composer is used for package management. Please require this ' .
'extension as package via Composer: ' .
'"composer require ' .
$extension->getComposerName() .
':^' .
$extension->getVersionString() .
'"</warning>');
$this->output->writeln(
'<warning>The extension ' . $extensionKey
. ' can not be downloaded since Composer is used for package management. Please require this '
. 'extension as package via Composer: "composer require ' . $extension->getComposerName()
. ':^' . $extension->getVersionString() . '"</warning>'
);
}
if ($updateSuccessful) {
......
......@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Install\Updates;
use Doctrine\DBAL\DBALException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
......@@ -30,14 +31,14 @@ use TYPO3\CMS\Core\Utility\PathUtility;
* Upgrade wizard which goes through all files referenced in backend_layout.icon
* and creates sys_file records as well as sys_file_reference records for each hit.
*/
class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwareInterface
class BackendLayoutIconUpdateWizard implements UpgradeWizardInterface, ChattyInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var string
* @var OutputInterface
*/
protected $title = 'Migrate all file relations from backend_layout.icon to sys_file_references';
protected $output;
/**
* @var ResourceStorage
......@@ -74,77 +75,93 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
protected $targetPath = '_migrated/backend_layouts/';
/**
* Checks if an update is needed
*
* @param string &$description The description for the update
*
* @return bool TRUE if an update is needed, FALSE otherwise
* @return string Unique identifier of this updater
*/
public function checkForUpdate(&$description)
public function getIdentifier(): string
{
if ($this->isWizardDone()) {
return false;
}
return 'backendLayoutIcons';
}
// If there are no valid records, the wizard can be marked as done directly
$dbQueries = [];
$records = $this->getRecordsFromTable($dbQueries);
if (empty($records)) {
$this->markWizardAsDone();
return false;
}
/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Migrate all file relations from backend_layout.icon to sys_file_references';
}
/**
* @return string Longer description of this updater
*/
public function getDescription(): string
{
return 'This update wizard goes through all files that are referenced in the'
. ' backend_layout.icon field and adds the files to the FAL File Index.'
. ' It also moves the files from uploads/ to the fileadmin/_migrated/ path.';
}
/**
* @return bool True if there are records to update
*/
public function updateNecessary(): bool
{
return !empty($this->getRecordsFromTable());
}
$description = 'This update wizard goes through all files that are referenced in the backend_layout.icon field'
. ' and adds the files to the FAL File Index.<br />'
. 'It also moves the files from uploads/ to the fileadmin/_migrated/ path.';
/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
return true;
/**
* @param OutputInterface $output
*/
public function setOutput(OutputInterface $output): void
{
$this->output = $output;
}
/**
* Performs the database update.
* Performs the configuration update.
*
* @param array &$dbQueries Queries done in this update
* @param string &$customMessage Custom message
* @return bool TRUE on success, FALSE on error
* @return bool
*/
public function performUpdate(array &$dbQueries, &$customMessage)
public function executeUpdate(): bool
{
$customMessage = '';
$result = true;
try {
$storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
$this->storage = $storages[0];
$records = $this->getRecordsFromTable($dbQueries);
$records = $this->getRecordsFromTable();
foreach ($records as $record) {
$this->migrateField($record, $customMessage, $dbQueries);
$this->migrateField($record);
}
$this->markWizardAsDone();
} catch (\Exception $e) {
$customMessage .= PHP_EOL . $e->getMessage();
// If something goes wrong, migrateField() logs an error
$result = false;
}
return empty($customMessage);
return $result;
}
/**
* Get records from table where the field to migrate is not empty (NOT NULL and != '')
* and also not numeric (which means that it is migrated)
*
* @param array $dbQueries
*
* @return array
* @throws \RuntimeException
*/
protected function getRecordsFromTable(&$dbQueries)
protected function getRecordsFromTable()
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
$queryBuilder->getRestrictions()->removeAll();
try {
$result = $queryBuilder
return $queryBuilder
->select('uid', 'pid', $this->fieldToMigrate)
->from($this->table)
->where(
......@@ -160,11 +177,8 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
)
)
->orderBy('uid')
->execute();
$dbQueries[] = $queryBuilder->getSQL();
return $result->fetchAll();
->execute()
->fetchAll();
} catch (DBALException $e) {
throw new \RuntimeException(
'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
......@@ -177,12 +191,9 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
* Migrates a single field.
*
* @param array $row
* @param string $customMessage
* @param array $dbQueries
*
* @throws \Exception
*/
protected function migrateField($row, &$customMessage, &$dbQueries)
protected function migrateField($row)
{
$fieldItems = GeneralUtility::trimExplode(',', $row[$this->fieldToMigrate], true);
if (empty($fieldItems) || is_numeric($row[$this->fieldToMigrate])) {
......@@ -240,7 +251,6 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
$file = $this->storage->getFile($this->targetPath . $item);
$fileUid = $file->getUid();
} catch (\InvalidArgumentException $e) {
// no file found, no reference can be set
$this->logger->notice(
'File ' . $this->sourcePath . $item . ' does not exist. Reference was not migrated.',
......@@ -250,16 +260,14 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
'field' => $this->fieldToMigrate,
]
);
$format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
$message = sprintf(
$this->output->writeln(sprintf(
$format,
$this->sourcePath . $item,
$this->table,
$row['uid'],
$this->fieldToMigrate
);
$customMessage .= PHP_EOL . $message;
));
continue;
}
}
......@@ -280,7 +288,6 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
$queryBuilder->insert('sys_file_reference')->values($fields)->execute();
$dbQueries[] = str_replace(LF, ' ', $queryBuilder->getSQL());
++$i;
}
}
......@@ -295,7 +302,6 @@ class BackendLayoutIconUpdateWizard extends AbstractUpdate implements LoggerAwar
$queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
)
)->set($this->fieldToMigrate, $i)->execute();
$dbQueries[] = str_replace(LF, ' ', $queryBuilder->getSQL());
}
}
}
......@@ -20,20 +20,40 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Update backend user setting startModule if set to "help_aboutmodules" or "help_CshmanualCshmanual"
*/
class BackendUserStartModuleUpdate extends AbstractUpdate
class BackendUserStartModuleUpdate implements UpgradeWizardInterface
{
/**
* @var string
* @return string Unique identifier of this updater
*/
protected $title = 'Update backend user setting "startModule"';
public function getIdentifier(): string
{
return 'cshmanualBackendUsers';
}
/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Update backend user setting "startModule"';
}
/**
* @return string Longer description of this updater
*/
public function getDescription(): string
{
return 'The backend user setting startModule is changed for the extensions about/aboutmodules'
. ' and help/cshmanual. Update all backend users that use EXT:aboutmodules and'
. ' EXT:cshmanual as startModule.';
}
/**
* Checks if an update is needed
*
* @param string &$description The description for the update
* @return bool Whether an update is needed (TRUE) or not (FALSE)
*/
public function checkForUpdate(&$description)
public function updateNecessary(): bool
{
$statement = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('be_users')
......@@ -53,22 +73,26 @@ class BackendUserStartModuleUpdate extends AbstractUpdate
}
}
}
if ($needsExecution) {
$description = 'The backend user setting startModule is changed for the extensions about/aboutmodules and help/cshmanual. Update all'
. ' backend users that use EXT:aboutmodules and EXT:cshmanual as startModule.';
}
return $needsExecution;
}
/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
/**
* Performs the database update if backend user's startmodule is
* "help_aboutmodules" or "help_AboutmodulesAboutmodules" or "help_CshmanualCshmanual"
*
* @param array &$databaseQueries Queries done in this update
* @param string &$customMessage Custom message
* @return bool
*/
public function performUpdate(array &$databaseQueries, &$customMessage)
public function executeUpdate(): bool
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
$statement = $queryBuilder->select('uid', 'uc')->from('be_users')->execute();
......@@ -95,9 +119,8 @@ class BackendUserStartModuleUpdate extends AbstractUpdate
)
// Manual quoting and false as third parameter to have the final
// value in $databaseQueries and not a statement placeholder
->set('uc', serialize($userConfig));
$databaseQueries[] = $queryBuilder->getSQL();
$queryBuilder->execute();
->set('uc', serialize($userConfig))
->execute();
}
}
}
......
......@@ -20,24 +20,39 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Migrate bullet content element rendering from layout to bullets_type
*/
class BulletContentElementUpdate extends AbstractUpdate
class BulletContentElementUpdate implements UpgradeWizardInterface
{
/**
* @var string
* @return string Unique identifier of this updater
*/
protected $title = '[Optional] Migrate bullet content element rendering selector from layout to bullets_type';
public function getIdentifier(): string
{
return 'bulletContentElementUpdate';
}
/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Migrate bullet content element rendering selector from layout to bullets_type';
}
/**
* @return string Longer description of this updater
*/
public function getDescription(): string
{
return 'Rendering type field has been streamlined with fluid_styled_content.';
}
/**
* Checks if an update is needed
*
* @param string &$description The description for the update
* @return bool Whether an update is needed (TRUE) or not (FALSE)
*/
public function checkForUpdate(&$description)
public function updateNecessary(): bool
{
if ($this->isWizardDone()) {
return false;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$queryBuilder->getRestrictions()->removeAll();
$elementCount = $queryBuilder->count('uid')
......@@ -46,21 +61,27 @@ class BulletContentElementUpdate extends AbstractUpdate
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('bullets', \PDO::PARAM_STR)),
$queryBuilder->expr()->in('layout', [1, 2])
)
->execute()->fetchColumn(0);
if ($elementCount) {
$description = 'Rendering type field has been streamlined with fluid_styled_content.';
}
->execute()
->fetchColumn(0);
return (bool)$elementCount;
}
/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
/**
* Performs the database update
*
* @param array &$databaseQueries Queries done in this update
* @param string &$customMessage Custom message
* @return bool
*/
public function performUpdate(array &$databaseQueries, &$customMessage)
public function executeUpdate(): bool
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
$queryBuilder = $connection->createQueryBuilder();
......@@ -83,10 +104,8 @@ class BulletContentElementUpdate extends AbstractUpdate
)
->set('layout', 0, false)
->set('bullets_type', $record['layout']);
$databaseQueries[] = $queryBuilder->getSQL();
$queryBuilder->execute();
}
$this->markWizardAsDone();
return true;
}
}
......@@ -28,7 +28,7 @@ interface ChattyInterface
/**
* Setter injection for output into upgrade wizards
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param OutputInterface $output
*/
public function setOutput(OutputInterface $output): void;
}
</
<?php
namespace TYPO3\CMS\Install\Updates;
/*
......@@ -14,6 +15,7 @@ namespace TYPO3\CMS\Install\Updates;
* The TYPO3 project - inspiring people to share!
*/
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -21,52 +23,91 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Remove all backend users starting with _cli_
*/
class CommandLineBackendUserRemovalUpdate extends AbstractUpdate
class CommandLineBackendUserRemovalUpdate implements UpgradeWizardInterface, ChattyInterface, RepeatableInterface, ConfirmableInterface
{
/**
* @var string
* @var OutputInterface
*/
protected $output;
/**
* @var Confirmation
*/
protected $confirmation;
public function __construct()
{
$this->confirmation = new Confirmation(
'Are you sure?',
'The following backend users will be removed: ' . implode(', ', $this->getUnneededCommandLineUsers()),
true
);
}
/**
* @return string Unique identifier of this updater
*/
public function getIdentifier(): string
{
return 'commandLineBackendUserRemovalUpdate';
}
/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Remove unneeded CLI backend users';
}
/**
* @return string Longer description of this updater
*/
protected $title = 'Remove unneeded CLI backend users';
public function getDescription(): string
{
return 'The command line interface does not need to have custom _cli_* backend users anymore.'
. ' They can safely be deleted.';
}
/**
* Checks if an update is needed
*
* @param string &$description The description for the update
* @return bool Whether an update is needed (TRUE) or not (FALSE)
*/
public function checkForUpdate(&$description)
public function updateNecessary(): bool
{
if ($this->isWizardDone()) {
return false;
}
$needsExecution = false;
$usersFound = $this->getUnneededCommandLineUsers();
if (!empty($usersFound)) {
$needsExecution = true;
$description = 'The command line interface does not need to have custom _cli_* backend users anymore. They can safely be deleted.';
}
return $needsExecution;
}
/**
* Shows information on the next step of the page
* @param string $formFieldNamePrefix
* @return string
* @param OutputInterface $output
*/
public function getUserInput($formFieldNamePrefix)
public function setOutput(OutputInterface $output): void
{
$usersFound = $this->getUnneededCommandLineUsers();
return '<p>The following backend users will be deleted:</p><ul><li>' . implode('</li><li>', $usersFound) . '</li></ul>';
$this->output = $output;
}
/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class,
];
}
/**
* Performs the database update to set all be_users starting with _CLI_* to deleted
*
* @param array &$databaseQueries Queries done in this update
* @param string &$customMessage Custom message
* @return bool
*/
public function performUpdate(array &$databaseQueries, &$customMessage)