'core.dashboard', 'label' => 'Dashboard', 'group' => 'Core'], ['key' => 'core.settings', 'label' => 'Settings', 'group' => 'Core'], ['key' => 'core.navigation', 'label' => 'Navigation', 'group' => 'Core'], ['key' => 'core.accounts', 'label' => 'Accounts', 'group' => 'Core'], ['key' => 'core.plugins', 'label' => 'Plugins', 'group' => 'Core'], ['key' => 'module.pages', 'label' => 'Pages', 'group' => 'Modules'], ['key' => 'module.shortcodes', 'label' => 'Shortcodes', 'group' => 'Modules'], ['key' => 'module.blog', 'label' => 'Blog', 'group' => 'Modules'], ['key' => 'module.media', 'label' => 'Media', 'group' => 'Modules'], ['key' => 'module.newsletter', 'label' => 'Newsletter', 'group' => 'Modules'], ]; foreach (Plugins::all() as $plugin) { $slug = trim((string)($plugin['slug'] ?? '')); if ($slug === '') { continue; } $defs[] = [ 'key' => 'plugin.' . $slug, 'label' => (string)($plugin['name'] ?? ucfirst($slug)), 'group' => 'Plugins', ]; } return $defs; } public static function matrix(): array { $default = self::defaultMatrix(); $raw = Settings::get('role_permissions_json', ''); if ($raw === '') { return $default; } $decoded = json_decode($raw, true); if (!is_array($decoded)) { return $default; } foreach ($default as $perm => $roles) { $row = $decoded[$perm] ?? null; if (!is_array($row)) { continue; } foreach (['admin', 'manager', 'editor'] as $role) { if (array_key_exists($role, $row)) { $default[$perm][$role] = self::toBool($row[$role]); } } } return $default; } public static function saveMatrix(array $posted): void { $current = self::matrix(); foreach ($current as $permission => $roles) { foreach ($roles as $role => $allowed) { $current[$permission][$role] = isset($posted[$permission][$role]); } } Settings::set('role_permissions_json', json_encode($current, JSON_UNESCAPED_SLASHES)); } public static function can(string $role, string $permission): bool { $matrix = self::matrix(); if (!isset($matrix[$permission])) { return true; } return (bool)($matrix[$permission][$role] ?? false); } public static function routePermission(string $path): ?string { if ($path === '/admin') { return 'core.dashboard'; } if (!str_starts_with($path, '/admin/')) { return null; } $slug = trim((string)explode('/', trim(substr($path, strlen('/admin/')), '/'))[0]); if ($slug === '' || in_array($slug, ['login', 'logout', 'install', 'installer'], true)) { return null; } $coreMap = [ 'settings' => 'core.settings', 'navigation' => 'core.navigation', 'accounts' => 'core.accounts', 'plugins' => 'core.plugins', 'pages' => 'module.pages', 'shortcodes' => 'module.shortcodes', 'blog' => 'module.blog', 'media' => 'module.media', 'newsletter' => 'module.newsletter', ]; if (isset($coreMap[$slug])) { return $coreMap[$slug]; } return 'plugin.' . $slug; } private static function defaultMatrix(): array { $matrix = []; foreach (self::definitions() as $def) { $key = (string)$def['key']; $matrix[$key] = [ 'admin' => true, 'manager' => true, 'editor' => false, ]; } foreach (['module.pages', 'module.shortcodes', 'module.blog'] as $editorAllowed) { if (isset($matrix[$editorAllowed])) { $matrix[$editorAllowed]['editor'] = true; } } return $matrix; } private static function toBool(mixed $value): bool { if (is_bool($value)) { return $value; } if (is_int($value) || is_float($value)) { return ((int)$value) === 1; } $v = strtolower(trim((string)$value)); return in_array($v, ['1', 'true', 'yes', 'on'], true); } }