RoleController.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. namespace App\Http\Controllers\Admin;
  3. use App\Http\Controllers\Controller;
  4. use App\Models\Permission;
  5. use App\Models\Role;
  6. use App\Models\User;
  7. use App\Services\Access\AccessService;
  8. use Illuminate\Http\RedirectResponse;
  9. use Illuminate\Http\Request;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Str;
  12. use Illuminate\View\View;
  13. class RoleController extends Controller
  14. {
  15. public function index(): View
  16. {
  17. return view('admin.roles.index', [
  18. 'active' => 'roles',
  19. 'title' => 'Роли и права',
  20. 'roles' => Role::query()
  21. ->withCount(['users' => fn ($query) => $query->withTrashed()])
  22. ->orderBy('sort')
  23. ->orderBy('name')
  24. ->get(),
  25. ]);
  26. }
  27. public function create(): View
  28. {
  29. return view('admin.roles.edit', $this->formData(new Role(), []));
  30. }
  31. public function store(Request $request, AccessService $accessService): RedirectResponse
  32. {
  33. $validated = $this->validateRole($request);
  34. $role = DB::transaction(function () use ($validated, $request, $accessService) {
  35. $role = Role::query()->create([
  36. 'slug' => $validated['slug'],
  37. 'name' => $validated['name'],
  38. 'description' => $validated['description'] ?? null,
  39. 'is_system' => false,
  40. 'is_active' => (bool) ($validated['is_active'] ?? true),
  41. 'sort' => (int) ($validated['sort'] ?? 100),
  42. ]);
  43. $this->syncRolePermissions($role, $request->input('permission_effects', []));
  44. $accessService->bumpCacheVersion();
  45. return $role;
  46. });
  47. return redirect()
  48. ->route('admin.roles.edit', $role)
  49. ->with('success', 'Роль создана.');
  50. }
  51. public function storeFromUser(User $user, Request $request, AccessService $accessService): RedirectResponse
  52. {
  53. $validated = $request->validate([
  54. 'name' => ['required', 'string', 'min:2', 'max:255'],
  55. 'slug' => ['nullable', 'string', 'alpha_dash', 'max:80', 'unique:roles,slug'],
  56. 'description' => ['nullable', 'string', 'max:1000'],
  57. ]);
  58. $slug = $validated['slug'] ?: Str::slug($validated['name']);
  59. if (!$slug) {
  60. $slug = 'role_' . $user->id;
  61. }
  62. $slug = $this->uniqueRoleSlug($slug);
  63. $role = DB::transaction(function () use ($validated, $slug, $user, $accessService) {
  64. $role = Role::query()->create([
  65. 'slug' => $slug,
  66. 'name' => $validated['name'],
  67. 'description' => $validated['description'] ?? 'Создана из совокупности прав пользователя ' . $user->name,
  68. 'is_system' => false,
  69. 'is_active' => true,
  70. 'sort' => 100,
  71. ]);
  72. $sync = [];
  73. foreach ($accessService->getEffectivePermissions($user) as $permissionSlug => $effect) {
  74. $sync[$permissionSlug] = $effect;
  75. }
  76. $this->syncRolePermissions($role, $sync);
  77. $accessService->bumpCacheVersion();
  78. return $role;
  79. });
  80. return redirect()
  81. ->route('admin.roles.edit', $role)
  82. ->with('success', 'Роль создана из прав пользователя.');
  83. }
  84. public function copy(Role $role, AccessService $accessService): RedirectResponse
  85. {
  86. $copy = DB::transaction(function () use ($role, $accessService) {
  87. $role->load('permissions');
  88. $copy = Role::query()->create([
  89. 'slug' => $this->uniqueRoleSlug($role->slug . '_copy'),
  90. 'name' => 'Копия ' . $role->name,
  91. 'description' => $role->description,
  92. 'is_system' => false,
  93. 'is_active' => true,
  94. 'sort' => (int) ($role->sort ?? 100),
  95. ]);
  96. $sync = [];
  97. foreach ($role->permissions as $permission) {
  98. $sync[$permission->id] = ['effect' => $permission->pivot->effect];
  99. }
  100. $copy->permissions()->sync($sync);
  101. $accessService->bumpCacheVersion();
  102. return $copy;
  103. });
  104. return redirect()
  105. ->route('admin.roles.edit', $copy)
  106. ->with('success', 'Роль скопирована.');
  107. }
  108. public function edit(Role $role): View
  109. {
  110. $effects = $role->permissions()
  111. ->pluck('role_permissions.effect', 'permissions.id')
  112. ->all();
  113. return view('admin.roles.edit', $this->formData($role, $effects));
  114. }
  115. public function update(Role $role, Request $request, AccessService $accessService): RedirectResponse
  116. {
  117. $validated = $this->validateRole($request, $role);
  118. DB::transaction(function () use ($role, $validated, $request, $accessService) {
  119. $role->update([
  120. 'name' => $validated['name'],
  121. 'description' => $validated['description'] ?? null,
  122. 'is_active' => $role->slug === Role::ADMIN ? true : (bool) ($validated['is_active'] ?? false),
  123. 'sort' => (int) ($validated['sort'] ?? $role->sort),
  124. ]);
  125. if ($role->slug === Role::ADMIN) {
  126. $this->syncRolePermissions(
  127. $role,
  128. Permission::query()->pluck('slug')->mapWithKeys(fn (string $slug): array => [$slug => 'allow'])->all()
  129. );
  130. } else {
  131. $this->syncRolePermissions($role, $request->input('permission_effects', []));
  132. }
  133. $accessService->bumpCacheVersion();
  134. });
  135. return redirect()
  136. ->route('admin.roles.edit', $role)
  137. ->with('success', 'Роль сохранена.');
  138. }
  139. public function destroy(Role $role, AccessService $accessService): RedirectResponse
  140. {
  141. if ($role->slug === Role::ADMIN) {
  142. return redirect()
  143. ->route('admin.roles.index')
  144. ->with('danger', 'Роль администратора нельзя удалить.');
  145. }
  146. $linkedUsers = User::withTrashed()
  147. ->where('role_id', $role->id)
  148. ->orWhere('role', $role->slug)
  149. ->count();
  150. if ($linkedUsers > 0) {
  151. return redirect()
  152. ->route('admin.roles.edit', $role)
  153. ->with('danger', 'Роль нельзя удалить, пока на неё ссылается хотя бы один пользователь.');
  154. }
  155. $role->delete();
  156. $accessService->bumpCacheVersion();
  157. return redirect()
  158. ->route('admin.roles.index')
  159. ->with('success', 'Роль удалена.');
  160. }
  161. private function formData(Role $role, array $effects): array
  162. {
  163. return [
  164. 'active' => 'roles',
  165. 'title' => $role->exists ? 'Редактирование роли' : 'Создание роли',
  166. 'role' => $role,
  167. 'permissionGroups' => Permission::getGroupedForUi(),
  168. 'permissionEffects' => $effects,
  169. ];
  170. }
  171. private function validateRole(Request $request, ?Role $role = null): array
  172. {
  173. $roleId = $role?->id ?: 'NULL';
  174. return $request->validate([
  175. 'slug' => [
  176. $role?->exists ? 'nullable' : 'required',
  177. 'string',
  178. 'alpha_dash',
  179. 'max:80',
  180. 'unique:roles,slug,' . $roleId,
  181. ],
  182. 'name' => ['required', 'string', 'min:2', 'max:255'],
  183. 'description' => ['nullable', 'string', 'max:1000'],
  184. 'is_active' => ['nullable', 'boolean'],
  185. 'sort' => ['nullable', 'integer', 'min:0', 'max:100000'],
  186. 'permission_effects' => ['nullable', 'array'],
  187. 'permission_effects.*' => ['nullable', 'in:none,allow,deny'],
  188. ]);
  189. }
  190. private function syncRolePermissions(Role $role, array $effects): void
  191. {
  192. $permissions = Permission::query()
  193. ->whereIn('id', array_filter(array_keys($effects), 'is_numeric'))
  194. ->orWhereIn('slug', array_filter(array_keys($effects), fn ($key): bool => !is_numeric($key)))
  195. ->get();
  196. $sync = [];
  197. foreach ($permissions as $permission) {
  198. $effect = $effects[$permission->id] ?? $effects[$permission->slug] ?? null;
  199. if (in_array($effect, ['allow', 'deny'], true)) {
  200. $sync[$permission->id] = ['effect' => $effect];
  201. }
  202. }
  203. $role->permissions()->sync($sync);
  204. }
  205. private function uniqueRoleSlug(string $slug): string
  206. {
  207. $baseSlug = $slug;
  208. $counter = 2;
  209. while (Role::query()->where('slug', $slug)->exists()) {
  210. $slug = $baseSlug . '_' . $counter++;
  211. }
  212. return $slug;
  213. }
  214. }