ldc-supportweb/app/Http/Controllers/Bo/TicketController.php

739 lines
25 KiB
PHP

<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Tickets\TicketTypeEnum;
use App\Enums\Users\GitlabUsersEnum;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\AssignTicketToUserRequest;
use App\Http\Requests\Bo\ChangeTicketStatus;
use App\Http\Requests\Bo\CreateMessage;
use App\Http\Requests\Bo\SortTickets;
use App\Http\Requests\Bo\StoreTicket;
use App\Http\Requests\Bo\UpdateMessage;
use App\Http\Requests\Bo\UpdateTicket;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Mail\NewTicket;
use App\Models\Message;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
class TicketController extends Controller
{
/**
* TicketController
*/
public function __construct()
{
parent::__construct();
$this->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 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<br>", $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);
}
}
// 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(
'<a href="%s" target="_blank">%s</a>',
$uploadedFile['url'],
__('Lien vers le fichier'),
);
}
return \sprintf("<br><br> Fichier %d : <br>%s<br>", $fileNb, $file);
}
}