|
@@ -0,0 +1,220 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Http\Controllers\Admin;
|
|
|
|
|
+
|
|
|
|
|
+use App\Http\Controllers\Controller;
|
|
|
|
|
+use App\Models\Permission;
|
|
|
|
|
+use App\Models\Role;
|
|
|
|
|
+use App\Models\User;
|
|
|
|
|
+use App\Services\Access\AccessService;
|
|
|
|
|
+use Illuminate\Http\RedirectResponse;
|
|
|
|
|
+use Illuminate\Http\Request;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
|
|
+use Illuminate\Support\Str;
|
|
|
|
|
+use Illuminate\View\View;
|
|
|
|
|
+
|
|
|
|
|
+class RoleController extends Controller
|
|
|
|
|
+{
|
|
|
|
|
+ public function index(): View
|
|
|
|
|
+ {
|
|
|
|
|
+ return view('admin.roles.index', [
|
|
|
|
|
+ 'active' => 'roles',
|
|
|
|
|
+ 'title' => 'Роли и права',
|
|
|
|
|
+ 'roles' => Role::query()
|
|
|
|
|
+ ->withCount(['users' => fn ($query) => $query->withTrashed()])
|
|
|
|
|
+ ->orderBy('sort')
|
|
|
|
|
+ ->orderBy('name')
|
|
|
|
|
+ ->get(),
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function create(): View
|
|
|
|
|
+ {
|
|
|
|
|
+ return view('admin.roles.edit', $this->formData(new Role(), []));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function store(Request $request, AccessService $accessService): RedirectResponse
|
|
|
|
|
+ {
|
|
|
|
|
+ $validated = $this->validateRole($request);
|
|
|
|
|
+
|
|
|
|
|
+ $role = DB::transaction(function () use ($validated, $request, $accessService) {
|
|
|
|
|
+ $role = Role::query()->create([
|
|
|
|
|
+ 'slug' => $validated['slug'],
|
|
|
|
|
+ 'name' => $validated['name'],
|
|
|
|
|
+ 'description' => $validated['description'] ?? null,
|
|
|
|
|
+ 'is_system' => false,
|
|
|
|
|
+ 'is_active' => (bool) ($validated['is_active'] ?? true),
|
|
|
|
|
+ 'sort' => (int) ($validated['sort'] ?? 100),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->syncRolePermissions($role, $request->input('permission_effects', []));
|
|
|
|
|
+ $accessService->bumpCacheVersion();
|
|
|
|
|
+
|
|
|
|
|
+ return $role;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.edit', $role)
|
|
|
|
|
+ ->with('success', 'Роль создана.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function storeFromUser(User $user, Request $request, AccessService $accessService): RedirectResponse
|
|
|
|
|
+ {
|
|
|
|
|
+ $validated = $request->validate([
|
|
|
|
|
+ 'name' => ['required', 'string', 'min:2', 'max:255'],
|
|
|
|
|
+ 'slug' => ['nullable', 'string', 'alpha_dash', 'max:80', 'unique:roles,slug'],
|
|
|
|
|
+ 'description' => ['nullable', 'string', 'max:1000'],
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $slug = $validated['slug'] ?: Str::slug($validated['name']);
|
|
|
|
|
+ if (!$slug) {
|
|
|
|
|
+ $slug = 'role_' . $user->id;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $baseSlug = $slug;
|
|
|
|
|
+ $counter = 2;
|
|
|
|
|
+ while (Role::query()->where('slug', $slug)->exists()) {
|
|
|
|
|
+ $slug = $baseSlug . '_' . $counter++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $role = DB::transaction(function () use ($validated, $slug, $user, $accessService) {
|
|
|
|
|
+ $role = Role::query()->create([
|
|
|
|
|
+ 'slug' => $slug,
|
|
|
|
|
+ 'name' => $validated['name'],
|
|
|
|
|
+ 'description' => $validated['description'] ?? 'Создана из совокупности прав пользователя ' . $user->name,
|
|
|
|
|
+ 'is_system' => false,
|
|
|
|
|
+ 'is_active' => true,
|
|
|
|
|
+ 'sort' => 100,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $sync = [];
|
|
|
|
|
+ foreach ($accessService->getEffectivePermissions($user) as $permissionSlug => $effect) {
|
|
|
|
|
+ $sync[$permissionSlug] = $effect;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->syncRolePermissions($role, $sync);
|
|
|
|
|
+ $accessService->bumpCacheVersion();
|
|
|
|
|
+
|
|
|
|
|
+ return $role;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.edit', $role)
|
|
|
|
|
+ ->with('success', 'Роль создана из прав пользователя.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function edit(Role $role): View
|
|
|
|
|
+ {
|
|
|
|
|
+ $effects = $role->permissions()
|
|
|
|
|
+ ->pluck('role_permissions.effect', 'permissions.id')
|
|
|
|
|
+ ->all();
|
|
|
|
|
+
|
|
|
|
|
+ return view('admin.roles.edit', $this->formData($role, $effects));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function update(Role $role, Request $request, AccessService $accessService): RedirectResponse
|
|
|
|
|
+ {
|
|
|
|
|
+ $validated = $this->validateRole($request, $role);
|
|
|
|
|
+
|
|
|
|
|
+ DB::transaction(function () use ($role, $validated, $request, $accessService) {
|
|
|
|
|
+ $role->update([
|
|
|
|
|
+ 'name' => $validated['name'],
|
|
|
|
|
+ 'description' => $validated['description'] ?? null,
|
|
|
|
|
+ 'is_active' => $role->slug === Role::ADMIN ? true : (bool) ($validated['is_active'] ?? false),
|
|
|
|
|
+ 'sort' => (int) ($validated['sort'] ?? $role->sort),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ if ($role->slug === Role::ADMIN) {
|
|
|
|
|
+ $this->syncRolePermissions(
|
|
|
|
|
+ $role,
|
|
|
|
|
+ Permission::query()->pluck('slug')->mapWithKeys(fn (string $slug): array => [$slug => 'allow'])->all()
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $this->syncRolePermissions($role, $request->input('permission_effects', []));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $accessService->bumpCacheVersion();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.edit', $role)
|
|
|
|
|
+ ->with('success', 'Роль сохранена.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function destroy(Role $role, AccessService $accessService): RedirectResponse
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($role->slug === Role::ADMIN) {
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.index')
|
|
|
|
|
+ ->with('danger', 'Роль администратора нельзя удалить.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $linkedUsers = User::withTrashed()
|
|
|
|
|
+ ->where('role_id', $role->id)
|
|
|
|
|
+ ->orWhere('role', $role->slug)
|
|
|
|
|
+ ->count();
|
|
|
|
|
+
|
|
|
|
|
+ if ($linkedUsers > 0) {
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.edit', $role)
|
|
|
|
|
+ ->with('danger', 'Роль нельзя удалить, пока на неё ссылается хотя бы один пользователь.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $role->delete();
|
|
|
|
|
+ $accessService->bumpCacheVersion();
|
|
|
|
|
+
|
|
|
|
|
+ return redirect()
|
|
|
|
|
+ ->route('admin.roles.index')
|
|
|
|
|
+ ->with('success', 'Роль удалена.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function formData(Role $role, array $effects): array
|
|
|
|
|
+ {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'active' => 'roles',
|
|
|
|
|
+ 'title' => $role->exists ? 'Редактирование роли' : 'Создание роли',
|
|
|
|
|
+ 'role' => $role,
|
|
|
|
|
+ 'permissionGroups' => Permission::getGroupedForUi(),
|
|
|
|
|
+ 'permissionEffects' => $effects,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function validateRole(Request $request, ?Role $role = null): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $roleId = $role?->id ?: 'NULL';
|
|
|
|
|
+
|
|
|
|
|
+ return $request->validate([
|
|
|
|
|
+ 'slug' => [
|
|
|
|
|
+ $role?->exists ? 'nullable' : 'required',
|
|
|
|
|
+ 'string',
|
|
|
|
|
+ 'alpha_dash',
|
|
|
|
|
+ 'max:80',
|
|
|
|
|
+ 'unique:roles,slug,' . $roleId,
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'name' => ['required', 'string', 'min:2', 'max:255'],
|
|
|
|
|
+ 'description' => ['nullable', 'string', 'max:1000'],
|
|
|
|
|
+ 'is_active' => ['nullable', 'boolean'],
|
|
|
|
|
+ 'sort' => ['nullable', 'integer', 'min:0', 'max:100000'],
|
|
|
|
|
+ 'permission_effects' => ['nullable', 'array'],
|
|
|
|
|
+ 'permission_effects.*' => ['nullable', 'in:none,allow,deny'],
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function syncRolePermissions(Role $role, array $effects): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $permissions = Permission::query()
|
|
|
|
|
+ ->whereIn('id', array_filter(array_keys($effects), 'is_numeric'))
|
|
|
|
|
+ ->orWhereIn('slug', array_filter(array_keys($effects), fn ($key): bool => !is_numeric($key)))
|
|
|
|
|
+ ->get();
|
|
|
|
|
+
|
|
|
|
|
+ $sync = [];
|
|
|
|
|
+ foreach ($permissions as $permission) {
|
|
|
|
|
+ $effect = $effects[$permission->id] ?? $effects[$permission->slug] ?? null;
|
|
|
|
|
+ if (in_array($effect, ['allow', 'deny'], true)) {
|
|
|
|
|
+ $sync[$permission->id] = ['effect' => $effect];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $role->permissions()->sync($sync);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|