| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- <?php
- declare(strict_types=1);
- namespace App\Http\Controllers;
- use App\Http\Requests\RenameDocumentationItemRequest;
- use App\Http\Requests\StoreDocumentationFileRequest;
- use App\Http\Requests\StoreDocumentationFolderRequest;
- use App\Http\Requests\UpdateDocumentationPermissionsRequest;
- use App\Models\DocumentationDocument;
- use App\Models\DocumentationDocumentVersion;
- use App\Models\DocumentationFolder;
- use App\Models\DocumentationFolderPermission;
- use App\Models\Role;
- use App\Models\User;
- use App\Services\DocumentationAccessService;
- use App\Services\DocumentationService;
- use Illuminate\Contracts\View\View;
- use Illuminate\Http\RedirectResponse;
- use Illuminate\Http\Request;
- use Illuminate\Http\UploadedFile;
- use Illuminate\Support\Collection as SupportCollection;
- use Illuminate\Support\Facades\Storage;
- use Symfony\Component\HttpFoundation\BinaryFileResponse;
- use Symfony\Component\HttpFoundation\StreamedResponse;
- class DocumentationController extends Controller
- {
- public function __construct(
- private readonly DocumentationAccessService $accessService,
- private readonly DocumentationService $documentationService,
- ) {}
- public function index(Request $request): View
- {
- $user = $this->user($request);
- $folders = $this->accessService->navigationFolders($user);
- $selectedFolder = $request->integer('folder')
- ? DocumentationFolder::query()->with('permissionRules')->findOrFail($request->integer('folder'))
- : $folders->first(fn (DocumentationFolder $folder): bool => (bool) $folder->getAttribute('can_read'));
- if ($selectedFolder) {
- abort_unless($this->accessService->canRead($user, $selectedFolder), 403);
- }
- $search = trim((string) $request->query('search', ''));
- $documents = $selectedFolder
- ? DocumentationDocument::query()
- ->where('folder_id', $selectedFolder->id)
- ->when($search !== '', fn ($query) => $query->where('name', 'like', "%{$search}%"))
- ->with(['currentVersion.file', 'currentVersion.creator', 'versions.file', 'versions.creator'])
- ->orderBy('name')
- ->paginate(20)
- ->withQueryString()
- : null;
- $canWrite = $selectedFolder && $this->accessService->canWrite($user, $selectedFolder);
- $canManagePermissions = $selectedFolder
- && $this->accessService->canManagePermissions($user, $selectedFolder);
- $selectedFolder?->loadMissing('permissionRules');
- $rules = $selectedFolder?->permissionRules ?? collect();
- return view('documents.index', [
- 'active' => 'documents',
- 'folders' => $folders,
- 'foldersByParent' => $folders->groupBy(fn (DocumentationFolder $folder): int => $folder->parent_id ?? 0),
- 'selectedFolder' => $selectedFolder,
- 'documents' => $documents,
- 'search' => $search,
- 'canCreateRoot' => $this->accessService->isAdministrator($user),
- 'canWrite' => (bool) $canWrite,
- 'canManagePermissions' => (bool) $canManagePermissions,
- 'roles' => $canManagePermissions ? Role::query()->where('is_active', true)->orderBy('name')->get() : collect(),
- 'users' => $canManagePermissions ? User::query()->with('roleModel')->orderBy('name')->get() : collect(),
- 'permissionValues' => $this->permissionValues($rules),
- ]);
- }
- public function storeFolder(StoreDocumentationFolderRequest $request): RedirectResponse
- {
- $user = $this->user($request);
- $parentId = $request->validated('parent_id');
- $parent = $parentId ? DocumentationFolder::query()->findOrFail((int) $parentId) : null;
- if ($parent) {
- abort_unless($this->accessService->canWrite($user, $parent), 403);
- } else {
- abort_unless($this->accessService->isAdministrator($user), 403);
- }
- $folder = $this->documentationService->createFolder($parent, (string) $request->validated('name'), $user);
- return redirect()
- ->route('documents.index', ['folder' => $folder->id])
- ->with('success', 'Папка создана.');
- }
- public function updateFolder(
- RenameDocumentationItemRequest $request,
- DocumentationFolder $folder,
- ): RedirectResponse {
- abort_unless($this->accessService->canWrite($this->user($request), $folder), 403);
- $this->documentationService->renameFolder($folder, (string) $request->validated('name'));
- return $this->folderRedirect($folder, 'Папка переименована.');
- }
- public function destroyFolder(Request $request, DocumentationFolder $folder): RedirectResponse
- {
- abort_unless($this->accessService->canWrite($this->user($request), $folder), 403);
- $parentId = $folder->parent_id;
- $this->documentationService->deleteFolder($folder);
- return redirect()
- ->route('documents.index', $parentId ? ['folder' => $parentId] : [])
- ->with('success', 'Папка и всё её содержимое удалены.');
- }
- public function updatePermissions(
- UpdateDocumentationPermissionsRequest $request,
- DocumentationFolder $folder,
- ): RedirectResponse {
- abort_unless($this->accessService->canManagePermissions($this->user($request), $folder), 403);
- $this->documentationService->updatePermissions($folder, $request->validated());
- return $this->folderRedirect($folder, 'Права папки обновлены.');
- }
- public function storeDocument(
- StoreDocumentationFileRequest $request,
- DocumentationFolder $folder,
- ): RedirectResponse {
- $user = $this->user($request);
- abort_unless($this->accessService->canWrite($user, $folder), 403);
- /** @var UploadedFile $file */
- $file = $request->file('file');
- $this->documentationService->createDocument(
- $folder,
- $request->validated('name'),
- $file,
- $user,
- );
- return $this->folderRedirect($folder, 'Документ загружен.');
- }
- public function updateDocument(
- RenameDocumentationItemRequest $request,
- DocumentationDocument $document,
- ): RedirectResponse {
- $document->loadMissing('folder');
- abort_unless($this->accessService->canWrite($this->user($request), $document->folder), 403);
- $this->documentationService->renameDocument($document, (string) $request->validated('name'));
- return $this->folderRedirect($document->folder, 'Документ переименован.');
- }
- public function destroyDocument(Request $request, DocumentationDocument $document): RedirectResponse
- {
- $document->loadMissing('folder');
- abort_unless($this->accessService->canWrite($this->user($request), $document->folder), 403);
- $folder = $document->folder;
- $this->documentationService->deleteDocument($document);
- return $this->folderRedirect($folder, 'Документ и все его версии удалены.');
- }
- public function storeVersion(
- StoreDocumentationFileRequest $request,
- DocumentationDocument $document,
- ): RedirectResponse {
- $document->loadMissing('folder');
- $user = $this->user($request);
- abort_unless($this->accessService->canWrite($user, $document->folder), 403);
- /** @var UploadedFile $file */
- $file = $request->file('file');
- $this->documentationService->addVersion($document, $file, $user);
- return $this->folderRedirect($document->folder, 'Новая версия загружена.');
- }
- public function downloadVersion(Request $request, DocumentationDocumentVersion $version): StreamedResponse
- {
- $version->loadMissing(['document.folder', 'file']);
- abort_unless($this->accessService->canRead($this->user($request), $version->document->folder), 403);
- abort_unless($version->file && Storage::disk('local')->exists($version->file->path), 404);
- return Storage::disk('local')->download($version->file->path, $version->file->original_name);
- }
- public function previewVersion(Request $request, DocumentationDocumentVersion $version): BinaryFileResponse
- {
- $version->loadMissing(['document.folder', 'file']);
- abort_unless($this->accessService->canRead($this->user($request), $version->document->folder), 403);
- abort_unless($version->file && Storage::disk('local')->exists($version->file->path), 404);
- $mimeType = Storage::disk('local')->mimeType($version->file->path);
- abort_unless(is_string($mimeType) && str_starts_with(strtolower($mimeType), 'image/'), 404);
- return response()->file(Storage::disk('local')->path($version->file->path), [
- 'Content-Type' => $mimeType,
- 'Content-Security-Policy' => "sandbox; default-src 'none'; img-src 'self' data:",
- 'X-Content-Type-Options' => 'nosniff',
- ]);
- }
- public function destroyVersion(Request $request, DocumentationDocumentVersion $version): RedirectResponse
- {
- $version->loadMissing('document.folder');
- $folder = $version->document->folder;
- abort_unless($this->accessService->canWrite($this->user($request), $folder), 403);
- $this->documentationService->deleteVersion($version);
- return $this->folderRedirect($folder, 'Версия полностью удалена.');
- }
- private function user(Request $request): User
- {
- /** @var User $user */
- $user = $request->user();
- return $user;
- }
- private function folderRedirect(DocumentationFolder $folder, string $message): RedirectResponse
- {
- return redirect()
- ->route('documents.index', ['folder' => $folder->id])
- ->with('success', $message);
- }
- /**
- * @param SupportCollection<int, DocumentationFolderPermission> $rules
- * @return array<string, mixed>
- */
- private function permissionValues(SupportCollection $rules): array
- {
- $all = $rules->firstWhere('subject_type', DocumentationFolderPermission::SUBJECT_ALL);
- return [
- 'all_read' => (bool) ($all?->can_read || $all?->can_write),
- 'all_write' => (bool) $all?->can_write,
- 'role_read_ids' => $rules
- ->where('subject_type', DocumentationFolderPermission::SUBJECT_ROLE)
- ->filter(fn (DocumentationFolderPermission $rule): bool => $rule->can_read || $rule->can_write)
- ->pluck('role_id')
- ->all(),
- 'role_write_ids' => $rules
- ->where('subject_type', DocumentationFolderPermission::SUBJECT_ROLE)
- ->where('can_write', true)
- ->pluck('role_id')
- ->all(),
- 'user_read_ids' => $rules
- ->where('subject_type', DocumentationFolderPermission::SUBJECT_USER)
- ->filter(fn (DocumentationFolderPermission $rule): bool => $rule->can_read || $rule->can_write)
- ->pluck('user_id')
- ->all(),
- 'user_write_ids' => $rules
- ->where('subject_type', DocumentationFolderPermission::SUBJECT_USER)
- ->where('can_write', true)
- ->pluck('user_id')
- ->all(),
- ];
- }
- }
|