authorizeResource(Ticket::class, 'ticket'); } /** * Tickets List * * @param SortTickets $request * @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory */ public function index(SortTickets $request) { // * Handle Ajax if ($request->ajax()) { $tickets = Ticket::with(['user', 'assignedUser']); if ($request->s) { $tickets = $tickets->where('text', 'LIKE', "%$request->s%") ->orWhere('id', $request->s) ->orWhereHas('user', function ($q) use ($request) { $q->where('name', 'LIKE', "%$request->s%"); }); } /** @var \Illuminate/Pagination/LengthAwarePaginator $tickets */ $tickets = $tickets->paginate(); return \response()->json($tickets->getCollection()->map(function (Ticket $ticket) { return [ 'label' => sprintf( 'Ticket %d (%s): %s', $ticket->id, $ticket->user ? $ticket->user->name : trans('Utilisateur supprimé'), Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35) ), 'value' => $ticket->id ]; })); } //end if /** @var \App\Models\User */ $user = auth()->user(); $tickets = Ticket::with(['site', 'labels']); if (!$user->isAdmin()) { $tickets->whereHas('site', function ($q) use ($user) { $q->whereHas('users', function ($q) use ($user) { $q->whereId($user->id); }); }); $tickets->where('is_internal', false); } else { $tickets->has('site'); } $tickets->where( 'status', ($request->st == TicketStatusEnum::archived->value) ? '=' : '<>', TicketStatusEnum::archived ); $tickets = $this->filterTickets($tickets, $request); $tickets = $this->searchTicket($tickets, $request); /** Custom pagination */ $tickets = $this->customPaginate($tickets, $request->pagination); $sites = Site::select('id', 'name')->orderBy('name')->get(); $status = TicketStatusEnum::cases(); return view('bo.pages.tickets.index', compact('tickets', 'sites', 'status')); } /** * Show Ticket * * @param Request $request * @param Ticket $ticket * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory */ public function show(Request $request, Ticket $ticket) { if ($request->ajax()) { return \response()->json([ 'label' => $ticket->formatName($ticket), 'value' => $ticket->id ]); } /** @var \App\Models\User */ $user = auth()->user(); if (!$user->can('update', $ticket)) { abort(403); } if ($ticket->is_internal && !$user->can('changeInternalStatus', $ticket)) { abort(403); } $messages = $ticket->messages()->orderBy('created_at', 'DESC')->get(); $isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count(); return view('bo.pages.tickets.show', compact('ticket', 'messages', 'isSubscribed')); } /** * Show create Form * * @return \Illuminate\Http\RedirectResponse */ public function create() { /** @var \App\Models\User */ $user = auth()->user(); if ($user->isAdmin()) { $sites = Site::orderBy('name')->get(); } else { $sites = $user->sites; } if (!count($sites)) { return redirect()->route('bo.sites.create') ->with('warning', trans('Vous devez tout d\'abord créer un site pour y ajouter un ticket')); } return view('bo.pages.tickets.create', compact('sites')); } /** * Create ticket * * @param StoreTicket $request * @return \Illuminate\Http\RedirectResponse */ public function store(StoreTicket $request) { try { return DB::transaction(function () use ($request) { /** @var \App\Models\Site */ $site = Site::findOrFail($request->site_id); /** @var \App\Models\User */ $user = auth()->user(); $ticket = new Ticket($request->validated()); /** @var null|array<\Illuminate\Http\UploadedFile> */ $fileTickets = $request->file_tickets; if (count($fileTickets) > 0) { foreach ($fileTickets as $key => $file) { if ($key === 0) { $ticket->file = FileStorageHelper::storeFile($ticket, $file); } else { $fileNumber = 'file' . $key + 1; $ticket->$fileNumber = FileStorageHelper::storeFile($ticket, $file); } } } $ticket->user()->associate(auth()->user()); $ticket->site()->associate($site); $ticket->saveOrFail(); if ($request->labels) { $ticket->labels()->sync($request->labels); } $user->subscriptions()->toggle($ticket->id); if (config('mail.MAIL_ADMIN')) { Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket)); } else { throw new \ErrorException('Missing MAIL_ADMIN in .env'); } return redirect()->route('bo.ticket.show', $ticket) ->with('success', trans('Ticket enregistré !')); }); } catch (\ErrorException $e) { report($e); Session::flash('error', trans('Une erreur est survenue')); } //end try return \back(); } /** * Show update form * * @param Ticket $ticket * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ public function edit(Ticket $ticket) { /** @var \App\Models\User */ $user = auth()->user(); if ($user->isAdmin()) { $sites = Site::orderBy('name')->get(); } else { $sites = $user->sites; } return view('bo.pages.tickets.edit', compact('ticket', 'sites')); } /** * Update Model * * @param UpdateTicket $request * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function update(UpdateTicket $request, Ticket $ticket) { $ticket->fill($request->validated()); try { return DB::transaction(function () use ($request, $ticket) { /** @var \App\Models\Site */ $site = Site::findOrFail($request->site_id); /** @var null|array<\Illuminate\Http\UploadedFile> */ $fileTickets = $request->file_tickets; if (isset($fileTickets) && count($fileTickets) > 0) { foreach ($fileTickets as $key => $file) { if ($key === 0) { $ticket->file = FileStorageHelper::storeFile($ticket, $file); } else { $fileNumber = 'file' . $key + 1; $ticket->$fileNumber = FileStorageHelper::storeFile($ticket, $file); } } } $ticket->labels()->sync($request->labels); $ticket->site()->associate($site); $ticket->saveOrFail(); return redirect()->route('bo.ticket.edit', $ticket->id) ->with('success', 'Modifications enregistrées !'); }); } catch (\ErrorException $e) { report($e); Session::flash('error', trans('Une erreur est survenue')); } //end try return \back(); } /** * Change ticket Status * * @param ChangeTicketStatus $request * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function changeStatus(ChangeTicketStatus $request, Ticket $ticket) { $ticket->fill($request->validated()); $ticket->saveOrFail(); return back() ->with('warning', trans('Le status à été modifié')); } /** * Add a new Message to the Ticket * * @param CreateMessage $request * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function createMessage(CreateMessage $request, Ticket $ticket) { try { return DB::transaction(function () use ($request, $ticket) { /** @var \Illuminate\Http\UploadedFile|null */ $filesMessage = $request->file_messages; $message = new Message($request->validated()); $message->user()->associate(auth()->user()); $message->ticket()->associate($ticket); /** @var \App\Models\User */ $user = auth()->user(); if ($request->subscribe) { if (!$user->subscriptions()->find($ticket->id)) { $user->subscriptions()->attach($ticket->id); } } elseif ($user->subscriptions()->find($ticket->id)) { $user->subscriptions()->detach($ticket->id); } if (isset($filesMessage) and $filesMessage) { foreach ($filesMessage as $key => $singleFile) { if ($key === 0) { $message->file = FileStorageHelper::storeFile($message, $singleFile); $message->saveOrFail(); } else { $fileNumber = 'file' . $key + 1; $message->$fileNumber = FileStorageHelper::storeFile($message, $singleFile); $message->saveOrFail(); } } } $message->saveOrFail(); return back() ->with('success', trans('Votre message à été ajouté')); }); } catch (\ErrorException $e) { report($e); Session::flash('error', trans('Une erreur est survenue')); } //end try return back(); } /** * Update Message * * @param UpdateMessage $request * @param Ticket $ticket * @param Message $message * @return \Illuminate\Http\RedirectResponse * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed */ public function updateMessage(UpdateMessage $request, Ticket $ticket, Message $message) { $message->message = $request->message; $message->saveOrFail(); return back() ->with('success', trans('Votre message à bien été modifié')); } /** * Change ticket status to archived * * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function setToArchive(Ticket $ticket) { $ticket->status = TicketStatusEnum::archived; $ticket->saveOrFail(); return redirect()->route('bo.ticket.index') ->with('warning', trans('Le ticket est archivé')); } /** * Subscribe to ticket * * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function subscribe(Ticket $ticket) { /** @var \App\Models\User */ $user = auth()->user(); $user->subscriptions()->toggle($ticket->id); if ($user->subscriptions()->where('ticket_id', $ticket->id)->exists()) { return back() ->with('success', trans('Vous avez été inscrit')); } return back()->with('warning', trans('Vous avez été désinscrit')); } /** * Reopen ticket * * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function reopen(Ticket $ticket) { $message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [ 'ticketName' => $ticket->user->name, 'ticketNumber' => $ticket->id, ]); /** @var \App\Enums\Tickets\TicketStatusEnum */ $oldStatus = $ticket->status; $ticket->status = TicketStatusEnum::new; $ticket->saveQuietly(); event(new TicketStatusChanged($ticket, $oldStatus, null, $message, false, false)); event(new NotifySubscriptions($ticket, $message, collect([$ticket->user]))); /** @var \App\Models\User */ $user = auth()->user(); $user->subscriptions()->toggle($ticket->id); return back()->with('warning', trans('Votre ticket à été réouvert.')); } /** * Assign ticket to user ticket * * @param AssignTicketToUserRequest $request * @param Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket) { /** @var \App\Models\User */ $user = auth()->user(); /** @var \App\Models\User|null */ if ($assignedUser = User::whereId($request->assign)->first()) { $message = trans(':admin a assigné le ticket n°:ticketNumber à :assignedAdmin', [ 'admin' => $user->name, 'ticketNumber' => $ticket->id, 'assignedAdmin' => $assignedUser->name, ]); $ticket->assignedUser()->associate($assignedUser); } else { $message = trans( ':admin a modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.', [ 'admin' => $user->name, 'ticketNumber' => $ticket->id, ] ); $ticket->assignedUser()->dissociate(); } $ticket->saveQuietly(); /** @var \App\Enums\Tickets\TicketStatusEnum */ $oldStatus = $ticket->status; // * Notification systeme doit etre privée. event(new TicketStatusChanged($ticket, $oldStatus, null, $message, true, false)); event(new NotifySubscriptions($ticket, $message, collect([$ticket->user]))); /** @var \App\Models\User */ $user = auth()->user(); $user->subscriptions()->toggle($ticket->id); if ($assignedUser) { return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [ 'assignedUser' => $assignedUser->name ])); } return back()->with('info', trans('Le ticket n\'est plus assigné à personne')); } // * PRIVATES /** * Filter Tickets request constructor * * @param Builder $tickets * @param Request $request * @return Builder * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh */ private function filterTickets(Builder $tickets, Request $request): Builder { //phpcs:enable if ($request->rst) { Session::remove('tickets.ass'); Session::remove('tickets.st'); Session::remove('tickets.pr'); Session::remove('tickets.sort_col'); Session::remove('tickets.sort_way'); Session::remove('tickets.search'); } if ($request->assr) { Session::remove('tickets.ass'); } if ($request->prs) { Session::remove('tickets.pr'); } if ($request->sts) { Session::remove('tickets.st'); } if ($request->sort_col) { Session::put('tickets.sort_col', $request->sort_col); } if ($request->sort_way) { Session::put('tickets.sort_way', $request->sort_way); } if ($request->pr) { Session::put('tickets.pr', $request->pr); } if ($request->st) { Session::put('tickets.st', $request->st); } if ($request->ass) { Session::put('tickets.ass', $request->ass); } Session::save(); if (Session::has('tickets.pr')) { $tickets->where('site_id', Session::get('tickets.pr')); } if (Session::has('tickets.ass')) { $tickets->whereHas('assignedUser', function ($q) { $q->whereId(Session::get('tickets.ass')); }); } if (Session::has('tickets.st')) { $tickets->where('status', Session::get('tickets.st')); } if ( Session::has('tickets.sort_col') and Session::has('tickets.sort_way') and in_array(Session::get('tickets.sort_col'), [ 'id', 'site_id', 'created_at', 'created_by', 'type', 'status', 'assigned_to' ]) ) { $tickets->orderBy( Session::get('tickets.sort_col'), Session::get('tickets.sort_way') ); } else { $tickets->orderBy('last_activity', 'DESC'); } return $tickets; } /** * Search ticket sql request constructor * * @param Builder $tickets * @param Request $request * @return Builder */ private function searchTicket(Builder $tickets, Request $request): Builder { if ($request->exists('search')) { if (empty($request->search)) { Session::remove('tickets.search'); } else { Session::put('tickets.search', $request->search); } } if (!Session::has('tickets.search')) { return $tickets; } return $this->searchQuery($tickets, Session::get('tickets.search'), function ($q) { $words = explode(' ', Session::get('tickets.search')); if (\is_array($words)) { foreach ($words as $word) { if (!strlen($word)) { continue; } $s = collect(TicketStatusEnum::cases())->filter(function (TicketStatusEnum $t) use ($word) { return Str::slug($t->label()) === Str::slug($word); })->map(function (TicketStatusEnum $ticket) { return $ticket->value; }); $t = collect(TicketTypeEnum::cases())->filter(function (TicketTypeEnum $s) use ($word) { return Str::slug($s->label()) === Str::slug($word); })->map(function (TicketTypeEnum $ticket) { return $ticket->value; }); $q->orWhere('id', $word) ->orWhereIn('status', $s) ->orWhereIn('type', $t) ->orWhereHas('user', function ($q) use ($word) { $q->where('name', 'LIKE', "%$word%"); }); } //end foreach } //end if }, 'text', 'title', 'file', 'file2', 'file3', 'file4', 'file5'); } /** * Get a paginate resource. * * @param \Illuminate\Http\Request $request * @return Illuminate\Http\JsonResponse */ public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse { return DB::transaction(function () use ($request) { $model = $request->targetModel; $search = $request->search; // * Handle Ajax $tickets = $model::with(['user', 'assignedUser']); if ($search) { $tickets = $tickets->where('text', 'LIKE', "%$search%") ->orWhere('id', $search) ->orWhereHas('user', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); }) ->orderBy("id", "ASC"); } return ToolboxHelper::jsonPaginate( $this->customPaginate($tickets, $request->pagination), 'formatName' ); }); } /** * Create a gitlab issue from a ticket. * * @param \App\Models\Ticket $ticket * @return \Illuminate\Http\RedirectResponse */ public function createGitlabIssue(Ticket $ticket) { $gitlabRepoId = $ticket->site->gitlab_project_id; $description = \sprintf("%s
", $ticket->text); $cLabel = match ($ticket->type) { TicketTypeEnum::bug => 'c-bug', TicketTypeEnum::evolution => 'c-feature', }; try { if (!\is_null($ticket->file)) { $uploadedFile = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file); $description .= $this->formatGitlabFiles($ticket, $uploadedFile, 1); } if (!\is_null($ticket->file2)) { $uploadedFile2 = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file2); $description .= $this->formatGitlabFiles($ticket, $uploadedFile2, 2); } if (!\is_null($ticket->file3)) { $uploadedFile3 = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file3); $description .= $this->formatGitlabFiles($ticket, $uploadedFile3, 3); } if (!\is_null($ticket->file4)) { $uploadedFile4 = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file4); $description .= $this->formatGitlabFiles($ticket, $uploadedFile4, 4); } if (!\is_null($ticket->file5)) { $uploadedFile5 = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file5); $description .= $this->formatGitlabFiles($ticket, $uploadedFile5, 5); } $issue = GitLab::issues()->create($gitlabRepoId, [ 'title' => 'ts-' . $ticket->id, 'description' => $description, 'labels' => \implode(",", [ 'c-support', $cLabel ]), 'assignee_id' => GitlabUsersEnum::sebastien, ]); } catch (\Exception $e) { \report($e); return back()->with('error', \trans("Une erreur est survenue")); } catch (\Throwable $e) { \report($e); return back()->with('error', \trans("Une erreur est survenue")); } //end try $ticket->gitlab_issue = $issue['web_url']; $ticket->save(); return back()->with( 'success', trans("L'issue a été créée sur Gitlab.") ); } /** * Format files for Gitlab issues * * @param \App\Models\Ticket $ticket * @param array $uploadedFile * @param integer $fileNb * @return string */ private function formatGitlabFiles(Ticket $ticket, array $uploadedFile, int $fileNb) { if (Str::endsWith($ticket->file, ['jpg', 'png', 'jpeg', 'pptx', 'docx', 'xslx', 'csv'])) { $file = \sprintf( "![Fichier joint %d](%s)", $fileNb, $uploadedFile['url'] ); } else { $file = \sprintf( '%s', $uploadedFile['url'], __('Lien vers le fichier'), ); } return \sprintf("

Fichier %d :
%s
", $fileNb, $file); } }