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 ); if (!$user->isAdmin() && $user->sites->count() === 1) { $request->merge([ 'pr' => $user->sites->first()->id, ]); } $tickets = $this->filterTickets($tickets, $request); $tickets = $this->searchTicket($tickets, $request); /** Sort columns with a query */ $this->sortQuery($tickets); /** Custom pagination */ $tickets = $this->customPaginate($tickets, $request->pagination); $sites = Site::select('id', 'name')->orderBy('name')->get(); $status = Arr::sort(TicketStatusEnum::cases(), function (TicketStatusEnum $value) { return $value->label(); }); $types = Arr::sort(TicketTypeEnum::cases(), function (TicketTypeEnum $value) { return $value->label(); }); return view('bo.pages.tickets.index', compact('tickets', 'sites', 'status', 'types')); } /** * 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); } $linkedTicket = $ticket->linkedTicket()->get(); $messages = $ticket->messages()->orderBy('created_at', 'DESC')->get(); $isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count(); return view('bo.pages.tickets.show', [ 'ticket' => $ticket, 'linkedTicket' => $linkedTicket, 'messages' => $messages, 'isSubscribed' => $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 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')); } /** * Link two tickets. * * @param LinkTicket $request * @param Ticket $ticket * * @return \Illuminate\Http\RedirectResponse */ public function linkTicket(LinkTicket $request, Ticket $ticket) { $linkedTicket = Ticket::findOrFail($request->validated())->first(); if ($ticket->site_id !== $linkedTicket->site_id) { return back()->with('error', trans('Le ticket que vous avez renseigné ne fait pas partie du même site.')); } if (!$ticket->linkedTicket()->where('linked_ticket_id', $linkedTicket->id)->exists()) { $ticket->linkedTicket()->attach($linkedTicket->id); $linkedTicket->linkedTicket()->attach($ticket->id); return back()->with('success', trans('Les tickets ont été liés avec succès.')); } return back()->with('error', trans('Les tickets sont déjà liés ensemble.')); } /** * Unlink two tickets. * * @param Ticket $ticket * @param Ticket $linkedTicket * * @return \Illuminate\Http\RedirectResponse */ public function unlinkTicket(Ticket $ticket, Ticket $linkedTicket) { $ticket->linkedTicket()->detach($linkedTicket->id); $linkedTicket->linkedTicket()->detach($ticket->id); return back()->with('warning', trans('Le lien entre les tickets à été supprimé.')); } // * 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.ty'); Session::remove('tickets.sort_col'); Session::remove('tickets.sort_way'); Session::remove('tickets.or'); 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->tys) { Session::remove('tickets.ty'); } 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->ord) { Session::remove('tickets.or'); } if ($request->pr) { Session::put('tickets.pr', $request->pr); } if ($request->st) { Session::put('tickets.st', $request->st); } if ($request->ty) { Session::put('tickets.ty', $request->ty); } if ($request->ass) { Session::put('tickets.ass', $request->ass); } if (Session::has('tickets.or')) { Session::remove('tickets.ass'); Session::remove('tickets.st'); Session::remove('tickets.ty'); Session::remove('tickets.sort_col'); Session::remove('tickets.sort_way'); Session::remove('tickets.search'); } if ($request->or && Session::has('tickets.pr')) { Session::put('tickets.or', $request->or); } 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.ty')) { $tickets->where('type', Session::get('tickets.ty')); } if (Session::has('tickets.or')) { $tickets->orderBy('order', 'ASC'); } if ( Session::has('tickets.sort_col') and Session::has('tickets.sort_way') and in_array(Session::get('tickets.sort_col'), [ 'type', 'title', 'assigned_to', 'date_mep', 'created_at', 'created_by' ]) ) { $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 { foreach ($ticket->getFiles() as $key => $file) { if (!\is_null($file)) { $uploadedFile = GitLab::projects()->uploadFile($gitlabRepoId, $file); $description .= $this->formatGitlabFiles($ticket, $uploadedFile, $key + 1); } } $issue = GitLab::issues()->create($gitlabRepoId, [ 'title' => 'ts-' . $ticket->id, 'description' => $description, 'labels' => \implode(",", [ 'c-support', $cLabel ]), 'assignee_id' => GitlabUsersEnum::nicolas, ]); } 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); } /** * Change order. * * @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request * @param mixed ...$params * @return \Illuminate\Http\RedirectResponse * @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé. */ public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse { $paramNames = collect(request()->route()->parameterNames); $className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly(); $className = "\\App\\Models\\{$className}"; $model = Arr::get($params, $paramNames->count() - 2); if (!class_exists($className)) { throw new \RuntimeException("The {$className} class doesn't exist."); } if (!\is_object($model)) { throw new \RuntimeException("The second last param isn't an object."); } if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) { throw new \RuntimeException("The {$className} doesn't match second last binded param."); } /** @var \Illuminate\Database\Eloquent\Model|null */ $model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first(); if (!$model) { return back()->with('error', trans('crud.messages.order_not_changed')); } return DB::transaction(function () use ($request, $className, $model) { /** @var \Illuminate\Support\Collection $models */ $models = $className::query() ->orderBy('order', 'ASC') ->where('site_id', $model->site_id) ->where('status', '!=', TicketStatusEnum::archived) ->where('id', '!=', $model->getKey()) ->where('order', $request->direction ? '<' : '>', $model->order) ->get(); /** @var \Illuminate\Database\Eloquent\Model|null $tmp */ $tmp = $request->direction ? $models->last() : $models->first(); if (!$tmp) { return back()->with('error', trans('crud.messages.order_not_changed')); } $newOrder = $tmp->getOriginal('order'); $tmp->update(['order' => $model->getOriginal('order')]); $model->update(['order' => $newOrder]); return redirect()->back()->with('success', trans('crud.messages.order_changed')); }); } }