Files

95 lines
3.6 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Admin;
use App\Models\AdminRole;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
/**
* Replace the legacy scoped roles Admin\{id}\admin by a role from admin_roles.
*
* Admin profiles used to get the role Admin\{id}\admin, with every permission of that moment and never synced
* (admin_roles has no "admin"). The permission checks now accept any role on the profile, so these roles are
* no longer needed. For every such role, on any installation:
* 1. each holder gets Admin\{id}\{default role} (admin_settings.default_admin_role, 'administrator'),
* created and synced from its definition when missing; nobody loses access;
* 2. then the legacy role is removed.
* Without a definition for the default role nothing changes. Running it again changes nothing.
* Called by `php artisan database:update`; run it with --dry-run first to see what it would do.
*/
class AdminRolesMigrate extends Command
{
protected $signature = 'admin-roles:migrate {--dry-run : Show what would change, change nothing}';
protected $description = 'Replace the legacy Admin\\{id}\\admin roles by the default role from admin_roles';
/** Matches Admin\{id}\admin */
public const LEGACY_PATTERN = '/^Admin\\\\(\d+)\\\\admin$/';
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
$defaultName = AdminRole::defaultName();
$definition = AdminRole::where('name', $defaultName)->first();
$legacyRoles = Role::where('guard_name', 'web')->get()
->filter(fn (Role $role) => preg_match(self::LEGACY_PATTERN, $role->name));
if ($legacyRoles->isEmpty()) {
$this->info('No legacy Admin\\{id}\\admin roles: nothing to do.');
return self::SUCCESS;
}
if (!$definition) {
$this->error("The default admin role '{$defaultName}' (admin_settings.default_admin_role) has no admin_roles definition: nothing changed.");
return self::FAILURE;
}
$this->info(($dryRun ? '[dry run] ' : '') . "Replacing {$legacyRoles->count()} legacy role(s) by '{$defaultName}':");
foreach ($legacyRoles as $legacy) {
preg_match(self::LEGACY_PATTERN, $legacy->name, $m);
$adminId = (int) $m[1];
$exists = Admin::whereKey($adminId)->exists();
$holderIds = DB::table('model_has_roles')
->where('role_id', $legacy->id)
->where('model_type', User::class)
->pluck('model_id');
$this->line(" {$legacy->name}: {$holderIds->count()} holder(s)" . ($exists ? '' : ' (admin profile no longer exists)'));
if ($exists) {
foreach (User::whereKey($holderIds)->get() as $user) {
$target = "Admin\\{$adminId}\\{$defaultName}";
if ($user->hasRole($target)) {
$this->line(" user #{$user->id} already has {$target}");
continue;
}
$this->line(" user #{$user->id} gets {$target}");
if (!$dryRun) {
$definition->assignScopedTo($user, $adminId);
}
}
}
$this->line(" remove {$legacy->name}");
if (!$dryRun) {
$legacy->delete();
}
}
if (!$dryRun) {
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->info('Done.');
}
return self::SUCCESS;
}
}