mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-18 01:28:58 +00:00
169 lines
5.7 KiB
PHP
169 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
namespace OCA\Forum\Migration;
|
|
|
|
use Closure;
|
|
use OCP\DB\ISchemaWrapper;
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
use OCP\IDBConnection;
|
|
use OCP\Migration\IOutput;
|
|
use OCP\Migration\SimpleMigrationStep;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Version 15 Migration:
|
|
* - Clean up duplicate roles that may exist from partial installations
|
|
* - Add unique constraint on role_type to prevent future duplicates
|
|
* - Re-run seeding to ensure all required data exists
|
|
*/
|
|
class Version15Date20260103000000 extends SimpleMigrationStep {
|
|
public function __construct(
|
|
private IDBConnection $db,
|
|
private LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Clean up duplicate roles before schema changes
|
|
*/
|
|
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
|
$output->info('Forum: Checking for duplicate roles...');
|
|
$this->cleanupDuplicateRoles($output);
|
|
}
|
|
|
|
/**
|
|
* @param IOutput $output
|
|
* @param Closure(): ISchemaWrapper $schemaClosure
|
|
* @param array $options
|
|
* @return ISchemaWrapper|null
|
|
*/
|
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
|
/** @var ISchemaWrapper $schema */
|
|
$schema = $schemaClosure();
|
|
|
|
// Add unique index on role_type to prevent future duplicates
|
|
if ($schema->hasTable('forum_roles')) {
|
|
$table = $schema->getTable('forum_roles');
|
|
|
|
// Check if the unique index already exists
|
|
$hasUniqueIndex = false;
|
|
foreach ($table->getIndexes() as $index) {
|
|
if ($index->getColumns() === ['role_type'] && $index->isUnique()) {
|
|
$hasUniqueIndex = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$hasUniqueIndex) {
|
|
$output->info('Forum: Adding unique constraint on role_type...');
|
|
$table->addUniqueIndex(['role_type'], 'forum_roles_role_type_uniq');
|
|
return $schema;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param IOutput $output
|
|
* @param Closure(): ISchemaWrapper $schemaClosure
|
|
* @param array $options
|
|
*/
|
|
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
|
// No-op: Seeding moved to Version16 which removes the incorrect unique constraint first
|
|
}
|
|
|
|
/**
|
|
* Remove duplicate roles, keeping only the first one of each type
|
|
*/
|
|
private function cleanupDuplicateRoles(IOutput $output): void {
|
|
$roleTypes = ['admin', 'moderator', 'default', 'guest'];
|
|
$duplicatesRemoved = 0;
|
|
|
|
foreach ($roleTypes as $roleType) {
|
|
// Find all roles of this type, ordered by ID
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->select('id')
|
|
->from('forum_roles')
|
|
->where($qb->expr()->eq('role_type', $qb->createNamedParameter($roleType, IQueryBuilder::PARAM_STR)))
|
|
->orderBy('id', 'ASC');
|
|
|
|
$result = $qb->executeQuery();
|
|
$roles = $result->fetchAll();
|
|
$result->closeCursor();
|
|
|
|
if (count($roles) <= 1) {
|
|
continue;
|
|
}
|
|
|
|
// Keep the first one, delete the rest
|
|
$keepId = (int)$roles[0]['id'];
|
|
$deleteIds = array_map(fn ($r) => (int)$r['id'], array_slice($roles, 1));
|
|
|
|
$this->logger->info('Forum migration: Found ' . count($deleteIds) . " duplicate '$roleType' roles, keeping ID $keepId");
|
|
$output->info(' Found ' . count($deleteIds) . " duplicate '$roleType' roles, keeping ID $keepId");
|
|
|
|
// Update user_roles to point to the kept role before deleting duplicates
|
|
foreach ($deleteIds as $deleteId) {
|
|
// Update forum_user_roles: reassign users from duplicate role to kept role
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->update('forum_user_roles')
|
|
->set('role_id', $qb->createNamedParameter($keepId, IQueryBuilder::PARAM_INT))
|
|
->where($qb->expr()->eq('role_id', $qb->createNamedParameter($deleteId, IQueryBuilder::PARAM_INT)));
|
|
|
|
try {
|
|
$qb->executeStatement();
|
|
} catch (\Exception $e) {
|
|
// Might fail due to unique constraint if user already has the kept role - that's fine
|
|
$this->logger->debug("Forum migration: Could not reassign user roles from $deleteId to $keepId: " . $e->getMessage());
|
|
}
|
|
|
|
// Delete orphaned user_roles entries (users who already had the kept role)
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->delete('forum_user_roles')
|
|
->where($qb->expr()->eq('role_id', $qb->createNamedParameter($deleteId, IQueryBuilder::PARAM_INT)));
|
|
$qb->executeStatement();
|
|
|
|
// Update forum_category_perms: reassign permissions from duplicate role to kept role
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->update('forum_category_perms')
|
|
->set('role_id', $qb->createNamedParameter($keepId, IQueryBuilder::PARAM_INT))
|
|
->where($qb->expr()->eq('role_id', $qb->createNamedParameter($deleteId, IQueryBuilder::PARAM_INT)));
|
|
|
|
try {
|
|
$qb->executeStatement();
|
|
} catch (\Exception $e) {
|
|
// Might fail due to unique constraint - that's fine
|
|
$this->logger->debug("Forum migration: Could not reassign category perms from $deleteId to $keepId: " . $e->getMessage());
|
|
}
|
|
|
|
// Delete orphaned category_perms entries
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->delete('forum_category_perms')
|
|
->where($qb->expr()->eq('role_id', $qb->createNamedParameter($deleteId, IQueryBuilder::PARAM_INT)));
|
|
$qb->executeStatement();
|
|
}
|
|
|
|
// Now delete the duplicate roles
|
|
$qb = $this->db->getQueryBuilder();
|
|
$qb->delete('forum_roles')
|
|
->where($qb->expr()->in('id', $qb->createNamedParameter($deleteIds, IQueryBuilder::PARAM_INT_ARRAY)));
|
|
$qb->executeStatement();
|
|
|
|
$duplicatesRemoved += count($deleteIds);
|
|
}
|
|
|
|
if ($duplicatesRemoved > 0) {
|
|
$output->info(" Removed $duplicatesRemoved duplicate roles");
|
|
$this->logger->info("Forum migration: Removed $duplicatesRemoved duplicate roles");
|
|
} else {
|
|
$output->info(' No duplicate roles found');
|
|
}
|
|
}
|
|
}
|