Files
cms-core/Classes/Upgrades/UserPermissionsForRenamedModulesMigration.php

152 lines
5.3 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Upgrades;
use TYPO3\CMS\Core\Attribute\UpgradeWizard;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @since 14.0
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
#[UpgradeWizard('userPermissionsForRenamedModulesMigration')]
class UserPermissionsForRenamedModulesMigration implements UpgradeWizardInterface
{
protected array $tables = [
'be_groups' => 'groupMods',
'be_users' => 'userMods',
];
/**
* @var array <string, string> an array with the old module identifier as key and the new one as value
*/
protected array $moduleRenaming = [
'web_list' => 'records',
'web_info' => 'content_status',
'workspaces_admin' => 'workspaces_publish',
'site_redirects' => 'redirects',
'web_linkvalidator' => 'linkvalidator_checklinks',
];
/**
* Modules that require a parent module to be accessible.
*
* @var array<string, string> Key: the new module identifier, Value: required parent module
*/
protected array $requiredParentModules = [
'redirects' => 'link_management',
'linkvalidator_checklinks' => 'link_management',
];
public function getTitle(): string
{
return 'Migrate module permissions';
}
public function getDescription(): string
{
return 'Migrate permissions for renamed modules in user and group module permissions. '
. 'Also adds required parent modules when a module has been moved to a new location in the module hierarchy.';
}
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class,
];
}
public function updateNecessary(): bool
{
return $this->migrate(true);
}
public function executeUpdate(): bool
{
return $this->migrate(false);
}
private function migrate(bool $dryRun): bool
{
$migrated = false;
foreach ($this->tables as $table => $field) {
$queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable($table);
$connection = $this->getConnectionPool()->getConnectionForTable($table);
$queryBuilder->select('uid', $field)
->from($table)
->executeQuery();
foreach ($queryBuilder->fetchAllAssociative() as $record) {
$originalModules = (string)($record[$field] ?? '');
$modules = explode(',', $originalModules);
$parentModulesToAdd = [];
$updatedModules = array_map(function ($module) use (&$parentModulesToAdd) {
$trimmedModule = trim($module);
if (isset($this->moduleRenaming[$trimmedModule])) {
$newModule = $this->moduleRenaming[$trimmedModule];
if (isset($this->requiredParentModules[$newModule])) {
$parentModulesToAdd[] = $this->requiredParentModules[$newModule];
}
return $newModule;
}
return $module;
}, $modules);
$trimmedModules = array_filter(array_map('trim', $updatedModules));
$deduplicatedModules = array_unique($trimmedModules);
// Check if existing modules (already renamed in a previous migration) need parent modules
// This handles the case where the wizard ran in v14.0 before $requiredParentModules existed
foreach ($deduplicatedModules as $module) {
if (isset($this->requiredParentModules[$module])) {
$parentModulesToAdd[] = $this->requiredParentModules[$module];
}
}
// Add parent modules if not already present
foreach (array_unique($parentModulesToAdd) as $parentModule) {
if (!in_array($parentModule, $deduplicatedModules, true)) {
$deduplicatedModules[] = $parentModule;
}
}
$newModules = implode(',', $deduplicatedModules);
if ($originalModules !== $newModules) {
if ($dryRun) {
return true;
}
$migrated = $connection->update(
$table,
[
$field => $newModules,
],
['uid' => (int)$record['uid']]
) > 0 || $migrated;
}
}
}
return $migrated;
}
protected function getConnectionPool(): ConnectionPool
{
return GeneralUtility::makeInstance(ConnectionPool::class);
}
}